diff --git a/client/src/client/Client.java b/client/src/client/Client.java index 571da67..7dcb511 100755 --- a/client/src/client/Client.java +++ b/client/src/client/Client.java @@ -130,6 +130,17 @@ import common.item.ItemControl; import common.item.ItemStack; import common.log.Log; import common.log.LogLevel; +import common.net.bootstrap.Bootstrap; +import common.net.channel.Channel; +import common.net.channel.ChannelException; +import common.net.channel.ChannelHandler; +import common.net.channel.ChannelInitializer; +import common.net.channel.ChannelOption; +import common.net.channel.nio.NioEventLoopGroup; +import common.net.channel.socket.nio.NioSocketChannel; +import common.net.handler.timeout.ReadTimeoutHandler; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; import common.network.IThreadListener; import common.network.NetConnection; import common.network.PacketDecoder; @@ -162,17 +173,6 @@ import common.util.HitPosition.ObjectType; import common.world.LightType; import common.world.State; import common.world.World; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelException; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; /* Een net ganz funktionierndes Programm ... diff --git a/client/src/client/gui/GuiInfo.java b/client/src/client/gui/GuiInfo.java index 8c52e6c..8fe7c30 100644 --- a/client/src/client/gui/GuiInfo.java +++ b/client/src/client/gui/GuiInfo.java @@ -18,7 +18,7 @@ public class GuiInfo extends Gui { private static final String[] LIBRARIES = { "LWJGL 3.3.6+1 (GLFW + OpenGL)", - "Netty 4.0.23-Final" + "Netty 4.0.23-Final (modifiziert, verkleinert)" }; private static final String[] CODE = { "Albert Pham - WorldEdit (Snippets)", diff --git a/client/src/client/network/ClientLoginHandler.java b/client/src/client/network/ClientLoginHandler.java index c4d9a18..b0c4ed7 100755 --- a/client/src/client/network/ClientLoginHandler.java +++ b/client/src/client/network/ClientLoginHandler.java @@ -5,6 +5,8 @@ import java.security.PublicKey; import javax.crypto.SecretKey; import client.Client; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; import common.network.IClientLoginHandler; import common.network.NetConnection; import common.network.NetHandler; @@ -16,8 +18,6 @@ import common.packet.RPacketEnableCompression; import common.packet.RPacketLoginSuccess; import common.packet.RPacketRequestEncrypt; import common.util.EncryptUtil; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; public class ClientLoginHandler extends NetHandler implements IClientLoginHandler { private final Client gm; diff --git a/common/src/common/net/bootstrap/AbstractBootstrap.java b/common/src/common/net/bootstrap/AbstractBootstrap.java new file mode 100644 index 0000000..0bb9d8b --- /dev/null +++ b/common/src/common/net/bootstrap/AbstractBootstrap.java @@ -0,0 +1,474 @@ +/* + * 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 common.net.bootstrap; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.LinkedHashMap; +import java.util.Map; + +import common.net.channel.Channel; +import common.net.channel.ChannelException; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelFutureListener; +import common.net.channel.ChannelHandler; +import common.net.channel.ChannelOption; +import common.net.channel.ChannelPromise; +import common.net.channel.DefaultChannelPromise; +import common.net.channel.EventLoopGroup; +import common.net.util.AttributeKey; +import common.net.util.concurrent.EventExecutor; +import common.net.util.concurrent.GlobalEventExecutor; +import common.net.util.internal.StringUtil; + +/** + * {@link AbstractBootstrap} is a helper class that makes it easy to bootstrap a {@link Channel}. It support + * method-chaining to provide an easy way to configure the {@link AbstractBootstrap}. + * + *

When not used in a {@link ServerBootstrap} context, the {@link #bind()} methods are useful for connectionless + * transports such as datagram (UDP).

+ */ +public abstract class AbstractBootstrap, C extends Channel> implements Cloneable { + + private volatile EventLoopGroup group; + private volatile ChannelFactory channelFactory; + private volatile SocketAddress localAddress; + private final Map, Object> options = new LinkedHashMap, Object>(); + private final Map, Object> attrs = new LinkedHashMap, Object>(); + private volatile ChannelHandler handler; + + AbstractBootstrap() { + // Disallow extending from a different package. + } + + AbstractBootstrap(AbstractBootstrap bootstrap) { + group = bootstrap.group; + channelFactory = bootstrap.channelFactory; + handler = bootstrap.handler; + localAddress = bootstrap.localAddress; + synchronized (bootstrap.options) { + options.putAll(bootstrap.options); + } + synchronized (bootstrap.attrs) { + attrs.putAll(bootstrap.attrs); + } + } + + /** + * The {@link EventLoopGroup} which is used to handle all the events for the to-be-creates + * {@link Channel} + */ + + public B group(EventLoopGroup group) { + if (group == null) { + throw new NullPointerException("group"); + } + if (this.group != null) { + throw new IllegalStateException("group set already"); + } + this.group = group; + return (B) this; + } + + /** + * The {@link Class} which is used to create {@link Channel} instances from. + * You either use this or {@link #channelFactory(ChannelFactory)} if your + * {@link Channel} implementation has no no-args constructor. + */ + public B channel(Class channelClass) { + if (channelClass == null) { + throw new NullPointerException("channelClass"); + } + return channelFactory(new BootstrapChannelFactory(channelClass)); + } + + /** + * {@link ChannelFactory} which is used to create {@link Channel} instances from + * when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)} + * is not working for you because of some more complex needs. If your {@link Channel} implementation + * has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for + * simplify your code. + */ + + public B channelFactory(ChannelFactory channelFactory) { + if (channelFactory == null) { + throw new NullPointerException("channelFactory"); + } + if (this.channelFactory != null) { + throw new IllegalStateException("channelFactory set already"); + } + + this.channelFactory = channelFactory; + return (B) this; + } + + /** + * The {@link SocketAddress} which is used to bind the local "end" to. + * + */ + + public B localAddress(SocketAddress localAddress) { + this.localAddress = localAddress; + return (B) this; + } + + /** + * @see {@link #localAddress(SocketAddress)} + */ + public B localAddress(int inetPort) { + return localAddress(new InetSocketAddress(inetPort)); + } + + /** + * @see {@link #localAddress(SocketAddress)} + */ + public B localAddress(String inetHost, int inetPort) { + return localAddress(new InetSocketAddress(inetHost, inetPort)); + } + + /** + * @see {@link #localAddress(SocketAddress)} + */ + public B localAddress(InetAddress inetHost, int inetPort) { + return localAddress(new InetSocketAddress(inetHost, inetPort)); + } + + /** + * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got + * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}. + */ + + public B option(ChannelOption option, T value) { + if (option == null) { + throw new NullPointerException("option"); + } + if (value == null) { + synchronized (options) { + options.remove(option); + } + } else { + synchronized (options) { + options.put(option, value); + } + } + return (B) this; + } + + /** + * Allow to specify an initial attribute of the newly created {@link Channel}. If the {@code value} is + * {@code null}, the attribute of the specified {@code key} is removed. + */ + public B attr(AttributeKey key, T value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + synchronized (attrs) { + attrs.remove(key); + } + } else { + synchronized (attrs) { + attrs.put(key, value); + } + } + + + B b = (B) this; + return b; + } + + /** + * Validate all the parameters. Sub-classes may override this, but should + * call the super method in that case. + */ + + public B validate() { + if (group == null) { + throw new IllegalStateException("group not set"); + } + if (channelFactory == null) { + throw new IllegalStateException("channel or channelFactory not set"); + } + return (B) this; + } + + /** + * Returns a deep clone of this bootstrap which has the identical configuration. This method is useful when making + * multiple {@link Channel}s with similar settings. Please note that this method does not clone the + * {@link EventLoopGroup} deeply but shallowly, making the group a shared resource. + */ + @Override + + public abstract B clone(); + + /** + * Create a new {@link Channel} and register it with an {@link EventLoop}. + */ + public ChannelFuture register() { + validate(); + return initAndRegister(); + } + + /** + * Create a new {@link Channel} and bind it. + */ + public ChannelFuture bind() { + validate(); + SocketAddress localAddress = this.localAddress; + if (localAddress == null) { + throw new IllegalStateException("localAddress not set"); + } + return doBind(localAddress); + } + + /** + * Create a new {@link Channel} and bind it. + */ + public ChannelFuture bind(int inetPort) { + return bind(new InetSocketAddress(inetPort)); + } + + /** + * Create a new {@link Channel} and bind it. + */ + public ChannelFuture bind(String inetHost, int inetPort) { + return bind(new InetSocketAddress(inetHost, inetPort)); + } + + /** + * Create a new {@link Channel} and bind it. + */ + public ChannelFuture bind(InetAddress inetHost, int inetPort) { + return bind(new InetSocketAddress(inetHost, inetPort)); + } + + /** + * Create a new {@link Channel} and bind it. + */ + public ChannelFuture bind(SocketAddress localAddress) { + validate(); + if (localAddress == null) { + throw new NullPointerException("localAddress"); + } + return doBind(localAddress); + } + + private ChannelFuture doBind(final SocketAddress localAddress) { + final ChannelFuture regFuture = initAndRegister(); + final Channel channel = regFuture.channel(); + if (regFuture.cause() != null) { + return regFuture; + } + + final ChannelPromise promise; + if (regFuture.isDone()) { + promise = channel.newPromise(); + doBind0(regFuture, channel, localAddress, promise); + } else { + // Registration future is almost always fulfilled already, but just in case it's not. + promise = new PendingRegistrationPromise(channel); + regFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + doBind0(regFuture, channel, localAddress, promise); + } + }); + } + + return promise; + } + + final ChannelFuture initAndRegister() { + final Channel channel = channelFactory().newChannel(); + try { + init(channel); + } catch (Throwable t) { + channel.unsafe().closeForcibly(); + // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor + return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); + } + + ChannelFuture regFuture = group().register(channel); + if (regFuture.cause() != null) { + if (channel.isRegistered()) { + channel.close(); + } else { + channel.unsafe().closeForcibly(); + } + } + + // If we are here and the promise is not failed, it's one of the following cases: + // 1) If we attempted registration from the event loop, the registration has been completed at this point. + // i.e. It's safe to attempt bind() or connect() now because the channel has been registered. + // 2) If we attempted registration from the other thread, the registration request has been successfully + // added to the event loop's task queue for later execution. + // i.e. It's safe to attempt bind() or connect() now: + // because bind() or connect() will be executed *after* the scheduled registration task is executed + // because register(), bind(), and connect() are all bound to the same thread. + + return regFuture; + } + + abstract void init(Channel channel) throws Exception; + + private static void doBind0( + final ChannelFuture regFuture, final Channel channel, + final SocketAddress localAddress, final ChannelPromise promise) { + + // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up + // the pipeline in its channelRegistered() implementation. + channel.eventLoop().execute(new Runnable() { + @Override + public void run() { + if (regFuture.isSuccess()) { + channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + } else { + promise.setFailure(regFuture.cause()); + } + } + }); + } + + /** + * the {@link ChannelHandler} to use for serving the requests. + */ + + public B handler(ChannelHandler handler) { + if (handler == null) { + throw new NullPointerException("handler"); + } + this.handler = handler; + return (B) this; + } + + final SocketAddress localAddress() { + return localAddress; + } + + final ChannelFactory channelFactory() { + return channelFactory; + } + + final ChannelHandler handler() { + return handler; + } + + /** + * Return the configured {@link EventLoopGroup} or {@code null} if non is configured yet. + */ + public final EventLoopGroup group() { + return group; + } + + final Map, Object> options() { + return options; + } + + final Map, Object> attrs() { + return attrs; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(StringUtil.simpleClassName(this)); + buf.append('('); + if (group != null) { + buf.append("group: "); + buf.append(StringUtil.simpleClassName(group)); + buf.append(", "); + } + if (channelFactory != null) { + buf.append("channelFactory: "); + buf.append(channelFactory); + buf.append(", "); + } + if (localAddress != null) { + buf.append("localAddress: "); + buf.append(localAddress); + buf.append(", "); + } + synchronized (options) { + if (!options.isEmpty()) { + buf.append("options: "); + buf.append(options); + buf.append(", "); + } + } + synchronized (attrs) { + if (!attrs.isEmpty()) { + buf.append("attrs: "); + buf.append(attrs); + buf.append(", "); + } + } + if (handler != null) { + buf.append("handler: "); + buf.append(handler); + buf.append(", "); + } + if (buf.charAt(buf.length() - 1) == '(') { + buf.append(')'); + } else { + buf.setCharAt(buf.length() - 2, ')'); + buf.setLength(buf.length() - 1); + } + return buf.toString(); + } + + private static final class BootstrapChannelFactory implements ChannelFactory { + private final Class clazz; + + BootstrapChannelFactory(Class clazz) { + this.clazz = clazz; + } + + @Override + public T newChannel() { + try { + return clazz.newInstance(); + } catch (Throwable t) { + throw new ChannelException("Unable to create Channel from class " + clazz, t); + } + } + + @Override + public String toString() { + return StringUtil.simpleClassName(clazz) + ".class"; + } + } + + private static final class PendingRegistrationPromise extends DefaultChannelPromise { + private PendingRegistrationPromise(Channel channel) { + super(channel); + } + + @Override + protected EventExecutor executor() { + if (channel().isRegistered()) { + // If the registration was a success we can just call super.executor() which will return + // channel.eventLoop(). + // + // See https://github.com/netty/netty/issues/2586 + return super.executor(); + } + // The registration failed so we can only use the GlobalEventExecutor as last resort to notify. + return GlobalEventExecutor.INSTANCE; + } + } +} diff --git a/common/src/common/net/bootstrap/Bootstrap.java b/common/src/common/net/bootstrap/Bootstrap.java new file mode 100644 index 0000000..dd59fb0 --- /dev/null +++ b/common/src/common/net/bootstrap/Bootstrap.java @@ -0,0 +1,233 @@ +/* + * 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 common.net.bootstrap; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Map; +import java.util.Map.Entry; + +import common.net.channel.Channel; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelFutureListener; +import common.net.channel.ChannelOption; +import common.net.channel.ChannelPipeline; +import common.net.channel.ChannelPromise; +import common.net.util.AttributeKey; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A {@link Bootstrap} that makes it easy to bootstrap a {@link Channel} to use + * for clients. + * + *

The {@link #bind()} methods are useful in combination with connectionless transports such as datagram (UDP). + * For regular TCP connections, please use the provided {@link #connect()} methods.

+ */ +public final class Bootstrap extends AbstractBootstrap { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class); + + private volatile SocketAddress remoteAddress; + + public Bootstrap() { } + + private Bootstrap(Bootstrap bootstrap) { + super(bootstrap); + remoteAddress = bootstrap.remoteAddress; + } + + /** + * The {@link SocketAddress} to connect to once the {@link #connect()} method + * is called. + */ + public Bootstrap remoteAddress(SocketAddress remoteAddress) { + this.remoteAddress = remoteAddress; + return this; + } + + /** + * @see {@link #remoteAddress(SocketAddress)} + */ + public Bootstrap remoteAddress(String inetHost, int inetPort) { + remoteAddress = new InetSocketAddress(inetHost, inetPort); + return this; + } + + /** + * @see {@link #remoteAddress(SocketAddress)} + */ + public Bootstrap remoteAddress(InetAddress inetHost, int inetPort) { + remoteAddress = new InetSocketAddress(inetHost, inetPort); + return this; + } + + /** + * Connect a {@link Channel} to the remote peer. + */ + public ChannelFuture connect() { + validate(); + SocketAddress remoteAddress = this.remoteAddress; + if (remoteAddress == null) { + throw new IllegalStateException("remoteAddress not set"); + } + + return doConnect(remoteAddress, localAddress()); + } + + /** + * Connect a {@link Channel} to the remote peer. + */ + public ChannelFuture connect(String inetHost, int inetPort) { + return connect(new InetSocketAddress(inetHost, inetPort)); + } + + /** + * Connect a {@link Channel} to the remote peer. + */ + public ChannelFuture connect(InetAddress inetHost, int inetPort) { + return connect(new InetSocketAddress(inetHost, inetPort)); + } + + /** + * Connect a {@link Channel} to the remote peer. + */ + public ChannelFuture connect(SocketAddress remoteAddress) { + if (remoteAddress == null) { + throw new NullPointerException("remoteAddress"); + } + + validate(); + return doConnect(remoteAddress, localAddress()); + } + + /** + * Connect a {@link Channel} to the remote peer. + */ + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + if (remoteAddress == null) { + throw new NullPointerException("remoteAddress"); + } + validate(); + return doConnect(remoteAddress, localAddress); + } + + /** + * @see {@link #connect()} + */ + private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) { + final ChannelFuture regFuture = initAndRegister(); + final Channel channel = regFuture.channel(); + if (regFuture.cause() != null) { + return regFuture; + } + + final ChannelPromise promise = channel.newPromise(); + if (regFuture.isDone()) { + doConnect0(regFuture, channel, remoteAddress, localAddress, promise); + } else { + regFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + doConnect0(regFuture, channel, remoteAddress, localAddress, promise); + } + }); + } + + return promise; + } + + private static void doConnect0( + final ChannelFuture regFuture, final Channel channel, + final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { + + // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up + // the pipeline in its channelRegistered() implementation. + channel.eventLoop().execute(new Runnable() { + @Override + public void run() { + if (regFuture.isSuccess()) { + if (localAddress == null) { + channel.connect(remoteAddress, promise); + } else { + channel.connect(remoteAddress, localAddress, promise); + } + promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + } else { + promise.setFailure(regFuture.cause()); + } + } + }); + } + + @Override + + void init(Channel channel) throws Exception { + ChannelPipeline p = channel.pipeline(); + p.addLast(handler()); + + final Map, Object> options = options(); + synchronized (options) { + for (Entry, Object> e: options.entrySet()) { + try { + if (!channel.config().setOption((ChannelOption) e.getKey(), e.getValue())) { + logger.warn("Unknown channel option: " + e); + } + } catch (Throwable t) { + logger.warn("Failed to set a channel option: " + channel, t); + } + } + } + + final Map, Object> attrs = attrs(); + synchronized (attrs) { + for (Entry, Object> e: attrs.entrySet()) { + channel.attr((AttributeKey) e.getKey()).set(e.getValue()); + } + } + } + + @Override + public Bootstrap validate() { + super.validate(); + if (handler() == null) { + throw new IllegalStateException("handler not set"); + } + return this; + } + + @Override + + public Bootstrap clone() { + return new Bootstrap(this); + } + + @Override + public String toString() { + if (remoteAddress == null) { + return super.toString(); + } + + StringBuilder buf = new StringBuilder(super.toString()); + buf.setLength(buf.length() - 1); + buf.append(", remoteAddress: "); + buf.append(remoteAddress); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/common/src/common/net/bootstrap/ChannelFactory.java b/common/src/common/net/bootstrap/ChannelFactory.java new file mode 100644 index 0000000..d6e48e3 --- /dev/null +++ b/common/src/common/net/bootstrap/ChannelFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013 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 common.net.bootstrap; + +import common.net.channel.Channel; + +/** + * Factory that creates a new {@link Channel} on {@link Bootstrap#bind()}, {@link Bootstrap#connect()}, and + * {@link ServerBootstrap#bind()}. + */ +public interface ChannelFactory { + /** + * Creates a new channel. + */ + T newChannel(); +} diff --git a/common/src/common/net/bootstrap/ServerBootstrap.java b/common/src/common/net/bootstrap/ServerBootstrap.java new file mode 100644 index 0000000..96144f9 --- /dev/null +++ b/common/src/common/net/bootstrap/ServerBootstrap.java @@ -0,0 +1,332 @@ +/* + * 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 common.net.bootstrap; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import common.net.channel.Channel; +import common.net.channel.ChannelConfig; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelFutureListener; +import common.net.channel.ChannelHandler; +import common.net.channel.ChannelHandlerContext; +import common.net.channel.ChannelInboundHandlerAdapter; +import common.net.channel.ChannelInitializer; +import common.net.channel.ChannelOption; +import common.net.channel.ChannelPipeline; +import common.net.channel.EventLoopGroup; +import common.net.channel.ServerChannel; +import common.net.util.AttributeKey; +import common.net.util.internal.StringUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel} + * + */ +public final class ServerBootstrap extends AbstractBootstrap { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class); + + private final Map, Object> childOptions = new LinkedHashMap, Object>(); + private final Map, Object> childAttrs = new LinkedHashMap, Object>(); + private volatile EventLoopGroup childGroup; + private volatile ChannelHandler childHandler; + + public ServerBootstrap() { } + + private ServerBootstrap(ServerBootstrap bootstrap) { + super(bootstrap); + childGroup = bootstrap.childGroup; + childHandler = bootstrap.childHandler; + synchronized (bootstrap.childOptions) { + childOptions.putAll(bootstrap.childOptions); + } + synchronized (bootstrap.childAttrs) { + childAttrs.putAll(bootstrap.childAttrs); + } + } + + /** + * Specify the {@link EventLoopGroup} which is used for the parent (acceptor) and the child (client). + */ + @Override + public ServerBootstrap group(EventLoopGroup group) { + return group(group, group); + } + + /** + * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These + * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link SocketChannel} and + * {@link Channel}'s. + */ + public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { + super.group(parentGroup); + if (childGroup == null) { + throw new NullPointerException("childGroup"); + } + if (this.childGroup != null) { + throw new IllegalStateException("childGroup set already"); + } + this.childGroup = childGroup; + return this; + } + + /** + * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created + * (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set + * {@link ChannelOption}. + */ + public ServerBootstrap childOption(ChannelOption childOption, T value) { + if (childOption == null) { + throw new NullPointerException("childOption"); + } + if (value == null) { + synchronized (childOptions) { + childOptions.remove(childOption); + } + } else { + synchronized (childOptions) { + childOptions.put(childOption, value); + } + } + return this; + } + + /** + * Set the specific {@link AttributeKey} with the given value on every child {@link Channel}. If the value is + * {@code null} the {@link AttributeKey} is removed + */ + public ServerBootstrap childAttr(AttributeKey childKey, T value) { + if (childKey == null) { + throw new NullPointerException("childKey"); + } + if (value == null) { + childAttrs.remove(childKey); + } else { + childAttrs.put(childKey, value); + } + return this; + } + + /** + * Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s. + */ + public ServerBootstrap childHandler(ChannelHandler childHandler) { + if (childHandler == null) { + throw new NullPointerException("childHandler"); + } + this.childHandler = childHandler; + return this; + } + + /** + * Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null} + * if non is configured yet. + */ + public EventLoopGroup childGroup() { + return childGroup; + } + + @Override + void init(Channel channel) throws Exception { + final Map, Object> options = options(); + synchronized (options) { + channel.config().setOptions(options); + } + + final Map, Object> attrs = attrs(); + synchronized (attrs) { + for (Entry, Object> e: attrs.entrySet()) { + + AttributeKey key = (AttributeKey) e.getKey(); + channel.attr(key).set(e.getValue()); + } + } + + ChannelPipeline p = channel.pipeline(); + if (handler() != null) { + p.addLast(handler()); + } + + final EventLoopGroup currentChildGroup = childGroup; + final ChannelHandler currentChildHandler = childHandler; + final Entry, Object>[] currentChildOptions; + final Entry, Object>[] currentChildAttrs; + synchronized (childOptions) { + currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); + } + synchronized (childAttrs) { + currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); + } + + p.addLast(new ChannelInitializer() { + @Override + public void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ServerBootstrapAcceptor( + currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); + } + }); + } + + @Override + public ServerBootstrap validate() { + super.validate(); + if (childHandler == null) { + throw new IllegalStateException("childHandler not set"); + } + if (childGroup == null) { + logger.warn("childGroup is not set. Using parentGroup instead."); + childGroup = group(); + } + return this; + } + + + private static Entry, Object>[] newOptionArray(int size) { + return new Entry[size]; + } + + + private static Entry, Object>[] newAttrArray(int size) { + return new Entry[size]; + } + + private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter { + + private final EventLoopGroup childGroup; + private final ChannelHandler childHandler; + private final Entry, Object>[] childOptions; + private final Entry, Object>[] childAttrs; + + ServerBootstrapAcceptor( + EventLoopGroup childGroup, ChannelHandler childHandler, + Entry, Object>[] childOptions, Entry, Object>[] childAttrs) { + this.childGroup = childGroup; + this.childHandler = childHandler; + this.childOptions = childOptions; + this.childAttrs = childAttrs; + } + + @Override + + public void channelRead(ChannelHandlerContext ctx, Object msg) { + final Channel child = (Channel) msg; + + child.pipeline().addLast(childHandler); + + for (Entry, Object> e: childOptions) { + try { + if (!child.config().setOption((ChannelOption) e.getKey(), e.getValue())) { + logger.warn("Unknown channel option: " + e); + } + } catch (Throwable t) { + logger.warn("Failed to set a channel option: " + child, t); + } + } + + for (Entry, Object> e: childAttrs) { + child.attr((AttributeKey) e.getKey()).set(e.getValue()); + } + + try { + childGroup.register(child).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + forceClose(child, future.cause()); + } + } + }); + } catch (Throwable t) { + forceClose(child, t); + } + } + + private static void forceClose(Channel child, Throwable t) { + child.unsafe().closeForcibly(); + logger.warn("Failed to register an accepted channel: " + child, t); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + final ChannelConfig config = ctx.channel().config(); + if (config.isAutoRead()) { + // stop accept new connections for 1 second to allow the channel to recover + // See https://github.com/netty/netty/issues/1328 + config.setAutoRead(false); + ctx.channel().eventLoop().schedule(new Runnable() { + @Override + public void run() { + config.setAutoRead(true); + } + }, 1, TimeUnit.SECONDS); + } + // still let the exceptionCaught event flow through the pipeline to give the user + // a chance to do something with it + ctx.fireExceptionCaught(cause); + } + } + + @Override + + public ServerBootstrap clone() { + return new ServerBootstrap(this); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(super.toString()); + buf.setLength(buf.length() - 1); + buf.append(", "); + if (childGroup != null) { + buf.append("childGroup: "); + buf.append(StringUtil.simpleClassName(childGroup)); + buf.append(", "); + } + synchronized (childOptions) { + if (!childOptions.isEmpty()) { + buf.append("childOptions: "); + buf.append(childOptions); + buf.append(", "); + } + } + synchronized (childAttrs) { + if (!childAttrs.isEmpty()) { + buf.append("childAttrs: "); + buf.append(childAttrs); + buf.append(", "); + } + } + if (childHandler != null) { + buf.append("childHandler: "); + buf.append(childHandler); + buf.append(", "); + } + if (buf.charAt(buf.length() - 1) == '(') { + buf.append(')'); + } else { + buf.setCharAt(buf.length() - 2, ')'); + buf.setLength(buf.length() - 1); + } + + return buf.toString(); + } +} diff --git a/common/src/common/net/buffer/AbstractByteBuf.java b/common/src/common/net/buffer/AbstractByteBuf.java new file mode 100644 index 0000000..5f4f200 --- /dev/null +++ b/common/src/common/net/buffer/AbstractByteBuf.java @@ -0,0 +1,1190 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; + +import common.net.util.IllegalReferenceCountException; +import common.net.util.ResourceLeakDetector; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; + + +/** + * A skeletal implementation of a buffer. + */ +public abstract class AbstractByteBuf extends ByteBuf { + + static final ResourceLeakDetector leakDetector = new ResourceLeakDetector(ByteBuf.class); + + int readerIndex; + int writerIndex; + private int markedReaderIndex; + private int markedWriterIndex; + + private int maxCapacity; + + private SwappedByteBuf swappedBuf; + + protected AbstractByteBuf(int maxCapacity) { + if (maxCapacity < 0) { + throw new IllegalArgumentException("maxCapacity: " + maxCapacity + " (expected: >= 0)"); + } + this.maxCapacity = maxCapacity; + } + + @Override + public int maxCapacity() { + return maxCapacity; + } + + protected final void maxCapacity(int maxCapacity) { + this.maxCapacity = maxCapacity; + } + + @Override + public int readerIndex() { + return readerIndex; + } + + @Override + public ByteBuf readerIndex(int readerIndex) { + if (readerIndex < 0 || readerIndex > writerIndex) { + throw new IndexOutOfBoundsException(String.format( + "readerIndex: %d (expected: 0 <= readerIndex <= writerIndex(%d))", readerIndex, writerIndex)); + } + this.readerIndex = readerIndex; + return this; + } + + @Override + public int writerIndex() { + return writerIndex; + } + + @Override + public ByteBuf writerIndex(int writerIndex) { + if (writerIndex < readerIndex || writerIndex > capacity()) { + throw new IndexOutOfBoundsException(String.format( + "writerIndex: %d (expected: readerIndex(%d) <= writerIndex <= capacity(%d))", + writerIndex, readerIndex, capacity())); + } + this.writerIndex = writerIndex; + return this; + } + + @Override + public ByteBuf setIndex(int readerIndex, int writerIndex) { + if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity()) { + throw new IndexOutOfBoundsException(String.format( + "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))", + readerIndex, writerIndex, capacity())); + } + this.readerIndex = readerIndex; + this.writerIndex = writerIndex; + return this; + } + + @Override + public ByteBuf clear() { + readerIndex = writerIndex = 0; + return this; + } + + @Override + public boolean isReadable() { + return writerIndex > readerIndex; + } + + @Override + public boolean isReadable(int numBytes) { + return writerIndex - readerIndex >= numBytes; + } + + @Override + public boolean isWritable() { + return capacity() > writerIndex; + } + + @Override + public boolean isWritable(int numBytes) { + return capacity() - writerIndex >= numBytes; + } + + @Override + public int readableBytes() { + return writerIndex - readerIndex; + } + + @Override + public int writableBytes() { + return capacity() - writerIndex; + } + + @Override + public int maxWritableBytes() { + return maxCapacity() - writerIndex; + } + + @Override + public ByteBuf markReaderIndex() { + markedReaderIndex = readerIndex; + return this; + } + + @Override + public ByteBuf resetReaderIndex() { + readerIndex(markedReaderIndex); + return this; + } + + @Override + public ByteBuf markWriterIndex() { + markedWriterIndex = writerIndex; + return this; + } + + @Override + public ByteBuf resetWriterIndex() { + writerIndex = markedWriterIndex; + return this; + } + + @Override + public ByteBuf discardReadBytes() { + ensureAccessible(); + if (readerIndex == 0) { + return this; + } + + if (readerIndex != writerIndex) { + setBytes(0, this, readerIndex, writerIndex - readerIndex); + writerIndex -= readerIndex; + adjustMarkers(readerIndex); + readerIndex = 0; + } else { + adjustMarkers(readerIndex); + writerIndex = readerIndex = 0; + } + return this; + } + + @Override + public ByteBuf discardSomeReadBytes() { + ensureAccessible(); + if (readerIndex == 0) { + return this; + } + + if (readerIndex == writerIndex) { + adjustMarkers(readerIndex); + writerIndex = readerIndex = 0; + return this; + } + + if (readerIndex >= capacity() >>> 1) { + setBytes(0, this, readerIndex, writerIndex - readerIndex); + writerIndex -= readerIndex; + adjustMarkers(readerIndex); + readerIndex = 0; + } + return this; + } + + protected final void adjustMarkers(int decrement) { + int markedReaderIndex = this.markedReaderIndex; + if (markedReaderIndex <= decrement) { + this.markedReaderIndex = 0; + int markedWriterIndex = this.markedWriterIndex; + if (markedWriterIndex <= decrement) { + this.markedWriterIndex = 0; + } else { + this.markedWriterIndex = markedWriterIndex - decrement; + } + } else { + this.markedReaderIndex = markedReaderIndex - decrement; + markedWriterIndex -= decrement; + } + } + + @Override + public ByteBuf ensureWritable(int minWritableBytes) { + if (minWritableBytes < 0) { + throw new IllegalArgumentException(String.format( + "minWritableBytes: %d (expected: >= 0)", minWritableBytes)); + } + + if (minWritableBytes <= writableBytes()) { + return this; + } + + if (minWritableBytes > maxCapacity - writerIndex) { + throw new IndexOutOfBoundsException(String.format( + "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", + writerIndex, minWritableBytes, maxCapacity, this)); + } + + // Normalize the current capacity to the power of 2. + int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes); + + // Adjust to the new capacity. + capacity(newCapacity); + return this; + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + if (minWritableBytes < 0) { + throw new IllegalArgumentException(String.format( + "minWritableBytes: %d (expected: >= 0)", minWritableBytes)); + } + + if (minWritableBytes <= writableBytes()) { + return 0; + } + + if (minWritableBytes > maxCapacity - writerIndex) { + if (force) { + if (capacity() == maxCapacity()) { + return 1; + } + + capacity(maxCapacity()); + return 3; + } + } + + // Normalize the current capacity to the power of 2. + int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes); + + // Adjust to the new capacity. + capacity(newCapacity); + return 2; + } + + private int calculateNewCapacity(int minNewCapacity) { + final int maxCapacity = this.maxCapacity; + final int threshold = 1048576 * 4; // 4 MiB page + + if (minNewCapacity == threshold) { + return threshold; + } + + // If over threshold, do not double but just increase by threshold. + if (minNewCapacity > threshold) { + int newCapacity = minNewCapacity / threshold * threshold; + if (newCapacity > maxCapacity - threshold) { + newCapacity = maxCapacity; + } else { + newCapacity += threshold; + } + return newCapacity; + } + + // Not over threshold. Double up to 4 MiB, starting from 64. + int newCapacity = 64; + while (newCapacity < minNewCapacity) { + newCapacity <<= 1; + } + + return Math.min(newCapacity, maxCapacity); + } + + @Override + public ByteBuf order(ByteOrder endianness) { + if (endianness == null) { + throw new NullPointerException("endianness"); + } + if (endianness == order()) { + return this; + } + + SwappedByteBuf swappedBuf = this.swappedBuf; + if (swappedBuf == null) { + this.swappedBuf = swappedBuf = newSwappedByteBuf(); + } + return swappedBuf; + } + + /** + * Creates a new {@link SwappedByteBuf} for this {@link ByteBuf} instance. + */ + protected SwappedByteBuf newSwappedByteBuf() { + return new SwappedByteBuf(this); + } + + @Override + public byte getByte(int index) { + checkIndex(index); + return _getByte(index); + } + + protected abstract byte _getByte(int index); + + @Override + public boolean getBoolean(int index) { + return getByte(index) != 0; + } + + @Override + public short getUnsignedByte(int index) { + return (short) (getByte(index) & 0xFF); + } + + @Override + public short getShort(int index) { + checkIndex(index, 2); + return _getShort(index); + } + + protected abstract short _getShort(int index); + + @Override + public int getUnsignedShort(int index) { + return getShort(index) & 0xFFFF; + } + + @Override + public int getUnsignedMedium(int index) { + checkIndex(index, 3); + return _getUnsignedMedium(index); + } + + protected abstract int _getUnsignedMedium(int index); + + @Override + public int getMedium(int index) { + int value = getUnsignedMedium(index); + if ((value & 0x800000) != 0) { + value |= 0xff000000; + } + return value; + } + + @Override + public int getInt(int index) { + checkIndex(index, 4); + return _getInt(index); + } + + protected abstract int _getInt(int index); + + @Override + public long getUnsignedInt(int index) { + return getInt(index) & 0xFFFFFFFFL; + } + + @Override + public long getLong(int index) { + checkIndex(index, 8); + return _getLong(index); + } + + protected abstract long _getLong(int index); + + @Override + public char getChar(int index) { + return (char) getShort(index); + } + + @Override + public float getFloat(int index) { + return Float.intBitsToFloat(getInt(index)); + } + + @Override + public double getDouble(int index) { + return Double.longBitsToDouble(getLong(index)); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst) { + getBytes(index, dst, 0, dst.length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst) { + getBytes(index, dst, dst.writableBytes()); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int length) { + getBytes(index, dst, dst.writerIndex(), length); + dst.writerIndex(dst.writerIndex() + length); + return this; + } + + @Override + public ByteBuf setByte(int index, int value) { + checkIndex(index); + _setByte(index, value); + return this; + } + + protected abstract void _setByte(int index, int value); + + @Override + public ByteBuf setBoolean(int index, boolean value) { + setByte(index, value ? 1 : 0); + return this; + } + + @Override + public ByteBuf setShort(int index, int value) { + checkIndex(index, 2); + _setShort(index, value); + return this; + } + + protected abstract void _setShort(int index, int value); + + @Override + public ByteBuf setChar(int index, int value) { + setShort(index, value); + return this; + } + + @Override + public ByteBuf setMedium(int index, int value) { + checkIndex(index, 3); + _setMedium(index, value); + return this; + } + + protected abstract void _setMedium(int index, int value); + + @Override + public ByteBuf setInt(int index, int value) { + checkIndex(index, 4); + _setInt(index, value); + return this; + } + + protected abstract void _setInt(int index, int value); + + @Override + public ByteBuf setFloat(int index, float value) { + setInt(index, Float.floatToRawIntBits(value)); + return this; + } + + @Override + public ByteBuf setLong(int index, long value) { + checkIndex(index, 8); + _setLong(index, value); + return this; + } + + protected abstract void _setLong(int index, long value); + + @Override + public ByteBuf setDouble(int index, double value) { + setLong(index, Double.doubleToRawLongBits(value)); + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src) { + setBytes(index, src, 0, src.length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src) { + setBytes(index, src, src.readableBytes()); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int length) { + checkIndex(index, length); + if (src == null) { + throw new NullPointerException("src"); + } + if (length > src.readableBytes()) { + throw new IndexOutOfBoundsException(String.format( + "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src)); + } + + setBytes(index, src, src.readerIndex(), length); + src.readerIndex(src.readerIndex() + length); + return this; + } + + @Override + public ByteBuf setZero(int index, int length) { + if (length == 0) { + return this; + } + + checkIndex(index, length); + + int nLong = length >>> 3; + int nBytes = length & 7; + for (int i = nLong; i > 0; i --) { + setLong(index, 0); + index += 8; + } + if (nBytes == 4) { + setInt(index, 0); + } else if (nBytes < 4) { + for (int i = nBytes; i > 0; i --) { + setByte(index, (byte) 0); + index ++; + } + } else { + setInt(index, 0); + index += 4; + for (int i = nBytes - 4; i > 0; i --) { + setByte(index, (byte) 0); + index ++; + } + } + return this; + } + + @Override + public byte readByte() { + checkReadableBytes(1); + int i = readerIndex; + byte b = getByte(i); + readerIndex = i + 1; + return b; + } + + @Override + public boolean readBoolean() { + return readByte() != 0; + } + + @Override + public short readUnsignedByte() { + return (short) (readByte() & 0xFF); + } + + @Override + public short readShort() { + checkReadableBytes(2); + short v = _getShort(readerIndex); + readerIndex += 2; + return v; + } + + @Override + public int readUnsignedShort() { + return readShort() & 0xFFFF; + } + + @Override + public int readMedium() { + int value = readUnsignedMedium(); + if ((value & 0x800000) != 0) { + value |= 0xff000000; + } + return value; + } + + @Override + public int readUnsignedMedium() { + checkReadableBytes(3); + int v = _getUnsignedMedium(readerIndex); + readerIndex += 3; + return v; + } + + @Override + public int readInt() { + checkReadableBytes(4); + int v = _getInt(readerIndex); + readerIndex += 4; + return v; + } + + @Override + public long readUnsignedInt() { + return readInt() & 0xFFFFFFFFL; + } + + @Override + public long readLong() { + checkReadableBytes(8); + long v = _getLong(readerIndex); + readerIndex += 8; + return v; + } + + @Override + public char readChar() { + return (char) readShort(); + } + + @Override + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + @Override + public double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + @Override + public ByteBuf readBytes(int length) { + checkReadableBytes(length); + if (length == 0) { + return Unpooled.EMPTY_BUFFER; + } + + // Use an unpooled heap buffer because there's no way to mandate a user to free the returned buffer. + ByteBuf buf = Unpooled.buffer(length, maxCapacity); + buf.writeBytes(this, readerIndex, length); + readerIndex += length; + return buf; + } + + @Override + public ByteBuf readSlice(int length) { + ByteBuf slice = slice(readerIndex, length); + readerIndex += length; + return slice; + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + checkReadableBytes(length); + getBytes(readerIndex, dst, dstIndex, length); + readerIndex += length; + return this; + } + + @Override + public ByteBuf readBytes(byte[] dst) { + readBytes(dst, 0, dst.length); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst) { + readBytes(dst, dst.writableBytes()); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int length) { + if (length > dst.writableBytes()) { + throw new IndexOutOfBoundsException(String.format( + "length(%d) exceeds dst.writableBytes(%d) where dst is: %s", length, dst.writableBytes(), dst)); + } + readBytes(dst, dst.writerIndex(), length); + dst.writerIndex(dst.writerIndex() + length); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + checkReadableBytes(length); + getBytes(readerIndex, dst, dstIndex, length); + readerIndex += length; + return this; + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + int length = dst.remaining(); + checkReadableBytes(length); + getBytes(readerIndex, dst); + readerIndex += length; + return this; + } + + @Override + public int readBytes(GatheringByteChannel out, int length) + throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length); + readerIndex += readBytes; + return readBytes; + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + checkReadableBytes(length); + getBytes(readerIndex, out, length); + readerIndex += length; + return this; + } + + @Override + public ByteBuf skipBytes(int length) { + checkReadableBytes(length); + readerIndex += length; + return this; + } + + @Override + public ByteBuf writeBoolean(boolean value) { + writeByte(value ? 1 : 0); + return this; + } + + @Override + public ByteBuf writeByte(int value) { + ensureAccessible(); + ensureWritable(1); + _setByte(writerIndex++, value); + return this; + } + + @Override + public ByteBuf writeShort(int value) { + ensureAccessible(); + ensureWritable(2); + _setShort(writerIndex, value); + writerIndex += 2; + return this; + } + + @Override + public ByteBuf writeMedium(int value) { + ensureAccessible(); + ensureWritable(3); + _setMedium(writerIndex, value); + writerIndex += 3; + return this; + } + + @Override + public ByteBuf writeInt(int value) { + ensureAccessible(); + ensureWritable(4); + _setInt(writerIndex, value); + writerIndex += 4; + return this; + } + + @Override + public ByteBuf writeLong(long value) { + ensureAccessible(); + ensureWritable(8); + _setLong(writerIndex, value); + writerIndex += 8; + return this; + } + + @Override + public ByteBuf writeChar(int value) { + writeShort(value); + return this; + } + + @Override + public ByteBuf writeFloat(float value) { + writeInt(Float.floatToRawIntBits(value)); + return this; + } + + @Override + public ByteBuf writeDouble(double value) { + writeLong(Double.doubleToRawLongBits(value)); + return this; + } + + @Override + public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { + ensureAccessible(); + ensureWritable(length); + setBytes(writerIndex, src, srcIndex, length); + writerIndex += length; + return this; + } + + @Override + public ByteBuf writeBytes(byte[] src) { + writeBytes(src, 0, src.length); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src) { + writeBytes(src, src.readableBytes()); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int length) { + if (length > src.readableBytes()) { + throw new IndexOutOfBoundsException(String.format( + "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src)); + } + writeBytes(src, src.readerIndex(), length); + src.readerIndex(src.readerIndex() + length); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + ensureAccessible(); + ensureWritable(length); + setBytes(writerIndex, src, srcIndex, length); + writerIndex += length; + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuffer src) { + ensureAccessible(); + int length = src.remaining(); + ensureWritable(length); + setBytes(writerIndex, src); + writerIndex += length; + return this; + } + + @Override + public int writeBytes(InputStream in, int length) + throws IOException { + ensureAccessible(); + ensureWritable(length); + int writtenBytes = setBytes(writerIndex, in, length); + if (writtenBytes > 0) { + writerIndex += writtenBytes; + } + return writtenBytes; + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + ensureAccessible(); + ensureWritable(length); + int writtenBytes = setBytes(writerIndex, in, length); + if (writtenBytes > 0) { + writerIndex += writtenBytes; + } + return writtenBytes; + } + + @Override + public ByteBuf writeZero(int length) { + if (length == 0) { + return this; + } + + ensureWritable(length); + checkIndex(writerIndex, length); + + int nLong = length >>> 3; + int nBytes = length & 7; + for (int i = nLong; i > 0; i --) { + writeLong(0); + } + if (nBytes == 4) { + writeInt(0); + } else if (nBytes < 4) { + for (int i = nBytes; i > 0; i --) { + writeByte((byte) 0); + } + } else { + writeInt(0); + for (int i = nBytes - 4; i > 0; i --) { + writeByte((byte) 0); + } + } + return this; + } + + @Override + public ByteBuf copy() { + return copy(readerIndex, readableBytes()); + } + + @Override + public ByteBuf duplicate() { + return new DuplicatedByteBuf(this); + } + + @Override + public ByteBuf slice() { + return slice(readerIndex, readableBytes()); + } + + @Override + public ByteBuf slice(int index, int length) { + if (length == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return new SlicedByteBuf(this, index, length); + } + + @Override + public ByteBuffer nioBuffer() { + return nioBuffer(readerIndex, readableBytes()); + } + + @Override + public ByteBuffer[] nioBuffers() { + return nioBuffers(readerIndex, readableBytes()); + } + + @Override + public String toString(Charset charset) { + return toString(readerIndex, readableBytes(), charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + if (length == 0) { + return ""; + } + + ByteBuffer nioBuffer; + if (nioBufferCount() == 1) { + nioBuffer = nioBuffer(index, length); + } else { + nioBuffer = ByteBuffer.allocate(length); + getBytes(index, nioBuffer); + nioBuffer.flip(); + } + + return ByteBufUtil.decodeString(nioBuffer, charset); + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + return ByteBufUtil.indexOf(this, fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + return bytesBefore(readerIndex(), readableBytes(), value); + } + + @Override + public int bytesBefore(int length, byte value) { + checkReadableBytes(length); + return bytesBefore(readerIndex(), length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + int endIndex = indexOf(index, index + length, value); + if (endIndex < 0) { + return -1; + } + return endIndex - index; + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + int index = readerIndex; + int length = writerIndex - index; + ensureAccessible(); + return forEachByteAsc0(index, length, processor); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + checkIndex(index, length); + return forEachByteAsc0(index, length, processor); + } + + private int forEachByteAsc0(int index, int length, ByteBufProcessor processor) { + if (processor == null) { + throw new NullPointerException("processor"); + } + + if (length == 0) { + return -1; + } + + final int endIndex = index + length; + int i = index; + try { + do { + if (processor.process(_getByte(i))) { + i ++; + } else { + return i; + } + } while (i < endIndex); + } catch (Exception e) { + PlatformDependent.throwException(e); + } + + return -1; + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + int index = readerIndex; + int length = writerIndex - index; + ensureAccessible(); + return forEachByteDesc0(index, length, processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + checkIndex(index, length); + + return forEachByteDesc0(index, length, processor); + } + + private int forEachByteDesc0(int index, int length, ByteBufProcessor processor) { + + if (processor == null) { + throw new NullPointerException("processor"); + } + + if (length == 0) { + return -1; + } + + int i = index + length - 1; + try { + do { + if (processor.process(_getByte(i))) { + i --; + } else { + return i; + } + } while (i >= index); + } catch (Exception e) { + PlatformDependent.throwException(e); + } + + return -1; + } + + @Override + public int hashCode() { + return ByteBufUtil.hashCode(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof ByteBuf) { + return ByteBufUtil.equals(this, (ByteBuf) o); + } + return false; + } + + @Override + public int compareTo(ByteBuf that) { + return ByteBufUtil.compare(this, that); + } + + @Override + public String toString() { + if (refCnt() == 0) { + return StringUtil.simpleClassName(this) + "(freed)"; + } + + StringBuilder buf = new StringBuilder(); + buf.append(StringUtil.simpleClassName(this)); + buf.append("(ridx: "); + buf.append(readerIndex); + buf.append(", widx: "); + buf.append(writerIndex); + buf.append(", cap: "); + buf.append(capacity()); + if (maxCapacity != Integer.MAX_VALUE) { + buf.append('/'); + buf.append(maxCapacity); + } + + ByteBuf unwrapped = unwrap(); + if (unwrapped != null) { + buf.append(", unwrapped: "); + buf.append(unwrapped); + } + buf.append(')'); + return buf.toString(); + } + + protected final void checkIndex(int index) { + ensureAccessible(); + if (index < 0 || index >= capacity()) { + throw new IndexOutOfBoundsException(String.format( + "index: %d (expected: range(0, %d))", index, capacity())); + } + } + + protected final void checkIndex(int index, int fieldLength) { + ensureAccessible(); + if (fieldLength < 0) { + throw new IllegalArgumentException("length: " + fieldLength + " (expected: >= 0)"); + } + if (index < 0 || index > capacity() - fieldLength) { + throw new IndexOutOfBoundsException(String.format( + "index: %d, length: %d (expected: range(0, %d))", index, fieldLength, capacity())); + } + } + + protected final void checkSrcIndex(int index, int length, int srcIndex, int srcCapacity) { + checkIndex(index, length); + if (srcIndex < 0 || srcIndex > srcCapacity - length) { + throw new IndexOutOfBoundsException(String.format( + "srcIndex: %d, length: %d (expected: range(0, %d))", srcIndex, length, srcCapacity)); + } + } + + protected final void checkDstIndex(int index, int length, int dstIndex, int dstCapacity) { + checkIndex(index, length); + if (dstIndex < 0 || dstIndex > dstCapacity - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dstCapacity)); + } + } + + /** + * Throws an {@link IndexOutOfBoundsException} if the current + * {@linkplain #readableBytes() readable bytes} of this buffer is less + * than the specified value. + */ + protected final void checkReadableBytes(int minimumReadableBytes) { + ensureAccessible(); + if (minimumReadableBytes < 0) { + throw new IllegalArgumentException("minimumReadableBytes: " + minimumReadableBytes + " (expected: >= 0)"); + } + if (readerIndex > writerIndex - minimumReadableBytes) { + throw new IndexOutOfBoundsException(String.format( + "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s", + readerIndex, minimumReadableBytes, writerIndex, this)); + } + } + + /** + * Should be called by every method that tries to access the buffers content to check + * if the buffer was released before. + */ + protected final void ensureAccessible() { + if (refCnt() == 0) { + throw new IllegalReferenceCountException(0); + } + } +} diff --git a/common/src/common/net/buffer/AbstractByteBufAllocator.java b/common/src/common/net/buffer/AbstractByteBufAllocator.java new file mode 100644 index 0000000..821b772 --- /dev/null +++ b/common/src/common/net/buffer/AbstractByteBufAllocator.java @@ -0,0 +1,219 @@ +/* + * 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 common.net.buffer; + +import common.net.util.ResourceLeak; +import common.net.util.ResourceLeakDetector; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; + +/** + * Skeletal {@link ByteBufAllocator} implementation to extend. + */ +public abstract class AbstractByteBufAllocator implements ByteBufAllocator { + private static final int DEFAULT_INITIAL_CAPACITY = 256; + private static final int DEFAULT_MAX_COMPONENTS = 16; + + protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) { + ResourceLeak leak; + switch (ResourceLeakDetector.getLevel()) { + case SIMPLE: + leak = AbstractByteBuf.leakDetector.open(buf); + if (leak != null) { + buf = new SimpleLeakAwareByteBuf(buf, leak); + } + break; + case ADVANCED: + case PARANOID: + leak = AbstractByteBuf.leakDetector.open(buf); + if (leak != null) { + buf = new AdvancedLeakAwareByteBuf(buf, leak); + } + break; + } + return buf; + } + + private final boolean directByDefault; + private final ByteBuf emptyBuf; + + /** + * Instance use heap buffers by default + */ + protected AbstractByteBufAllocator() { + this(false); + } + + /** + * Create new instance + * + * @param preferDirect {@code true} if {@link #buffer(int)} should try to allocate a direct buffer rather than + * a heap buffer + */ + protected AbstractByteBufAllocator(boolean preferDirect) { + directByDefault = preferDirect && PlatformDependent.hasUnsafe(); + emptyBuf = new EmptyByteBuf(this); + } + + @Override + public ByteBuf buffer() { + if (directByDefault) { + return directBuffer(); + } + return heapBuffer(); + } + + @Override + public ByteBuf buffer(int initialCapacity) { + if (directByDefault) { + return directBuffer(initialCapacity); + } + return heapBuffer(initialCapacity); + } + + @Override + public ByteBuf buffer(int initialCapacity, int maxCapacity) { + if (directByDefault) { + return directBuffer(initialCapacity, maxCapacity); + } + return heapBuffer(initialCapacity, maxCapacity); + } + + @Override + public ByteBuf ioBuffer() { + if (PlatformDependent.hasUnsafe()) { + return directBuffer(DEFAULT_INITIAL_CAPACITY); + } + return heapBuffer(DEFAULT_INITIAL_CAPACITY); + } + + @Override + public ByteBuf ioBuffer(int initialCapacity) { + if (PlatformDependent.hasUnsafe()) { + return directBuffer(initialCapacity); + } + return heapBuffer(initialCapacity); + } + + @Override + public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) { + if (PlatformDependent.hasUnsafe()) { + return directBuffer(initialCapacity, maxCapacity); + } + return heapBuffer(initialCapacity, maxCapacity); + } + + @Override + public ByteBuf heapBuffer() { + return heapBuffer(DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE); + } + + @Override + public ByteBuf heapBuffer(int initialCapacity) { + return heapBuffer(initialCapacity, Integer.MAX_VALUE); + } + + @Override + public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) { + if (initialCapacity == 0 && maxCapacity == 0) { + return emptyBuf; + } + validate(initialCapacity, maxCapacity); + return newHeapBuffer(initialCapacity, maxCapacity); + } + + @Override + public ByteBuf directBuffer() { + return directBuffer(DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE); + } + + @Override + public ByteBuf directBuffer(int initialCapacity) { + return directBuffer(initialCapacity, Integer.MAX_VALUE); + } + + @Override + public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { + if (initialCapacity == 0 && maxCapacity == 0) { + return emptyBuf; + } + validate(initialCapacity, maxCapacity); + return newDirectBuffer(initialCapacity, maxCapacity); + } + + @Override + public CompositeByteBuf compositeBuffer() { + if (directByDefault) { + return compositeDirectBuffer(); + } + return compositeHeapBuffer(); + } + + @Override + public CompositeByteBuf compositeBuffer(int maxNumComponents) { + if (directByDefault) { + return compositeDirectBuffer(maxNumComponents); + } + return compositeHeapBuffer(maxNumComponents); + } + + @Override + public CompositeByteBuf compositeHeapBuffer() { + return compositeHeapBuffer(DEFAULT_MAX_COMPONENTS); + } + + @Override + public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) { + return new CompositeByteBuf(this, false, maxNumComponents); + } + + @Override + public CompositeByteBuf compositeDirectBuffer() { + return compositeDirectBuffer(DEFAULT_MAX_COMPONENTS); + } + + @Override + public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) { + return new CompositeByteBuf(this, true, maxNumComponents); + } + + private static void validate(int initialCapacity, int maxCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException("initialCapacity: " + initialCapacity + " (expectd: 0+)"); + } + if (initialCapacity > maxCapacity) { + throw new IllegalArgumentException(String.format( + "initialCapacity: %d (expected: not greater than maxCapacity(%d)", + initialCapacity, maxCapacity)); + } + } + + /** + * Create a heap {@link ByteBuf} with the given initialCapacity and maxCapacity. + */ + protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity); + + /** + * Create a direct {@link ByteBuf} with the given initialCapacity and maxCapacity. + */ + protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity); + + @Override + public String toString() { + return StringUtil.simpleClassName(this) + "(directByDefault: " + directByDefault + ')'; + } +} diff --git a/common/src/common/net/buffer/AbstractDerivedByteBuf.java b/common/src/common/net/buffer/AbstractDerivedByteBuf.java new file mode 100644 index 0000000..5fad3e7 --- /dev/null +++ b/common/src/common/net/buffer/AbstractDerivedByteBuf.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.nio.ByteBuffer; + +/** + * Abstract base class for {@link ByteBuf} implementations that wrap another + * {@link ByteBuf}. + */ +public abstract class AbstractDerivedByteBuf extends AbstractByteBuf { + + protected AbstractDerivedByteBuf(int maxCapacity) { + super(maxCapacity); + } + + @Override + public final int refCnt() { + return unwrap().refCnt(); + } + + @Override + public final ByteBuf retain() { + unwrap().retain(); + return this; + } + + @Override + public final ByteBuf retain(int increment) { + unwrap().retain(increment); + return this; + } + + @Override + public final boolean release() { + return unwrap().release(); + } + + @Override + public final boolean release(int decrement) { + return unwrap().release(decrement); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return nioBuffer(index, length); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return unwrap().nioBuffer(index, length); + } +} diff --git a/common/src/common/net/buffer/AbstractReferenceCountedByteBuf.java b/common/src/common/net/buffer/AbstractReferenceCountedByteBuf.java new file mode 100644 index 0000000..53ad03a --- /dev/null +++ b/common/src/common/net/buffer/AbstractReferenceCountedByteBuf.java @@ -0,0 +1,140 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import common.net.util.IllegalReferenceCountException; +import common.net.util.internal.PlatformDependent; + +/** + * Abstract base class for {@link ByteBuf} implementations that count references. + */ +public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf { + + private static final AtomicIntegerFieldUpdater refCntUpdater; + + static { + AtomicIntegerFieldUpdater updater = + PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); + if (updater == null) { + updater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); + } + refCntUpdater = updater; + } + + private volatile int refCnt = 1; + + protected AbstractReferenceCountedByteBuf(int maxCapacity) { + super(maxCapacity); + } + + @Override + public final int refCnt() { + return refCnt; + } + + /** + * An unsafe operation intended for use by a subclass that sets the reference count of the buffer directly + */ + protected final void setRefCnt(int refCnt) { + this.refCnt = refCnt; + } + + @Override + public ByteBuf retain() { + for (;;) { + int refCnt = this.refCnt; + if (refCnt == 0) { + throw new IllegalReferenceCountException(0, 1); + } + if (refCnt == Integer.MAX_VALUE) { + throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1); + } + if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) { + break; + } + } + return this; + } + + @Override + public ByteBuf retain(int increment) { + if (increment <= 0) { + throw new IllegalArgumentException("increment: " + increment + " (expected: > 0)"); + } + + for (;;) { + int refCnt = this.refCnt; + if (refCnt == 0) { + throw new IllegalReferenceCountException(0, increment); + } + if (refCnt > Integer.MAX_VALUE - increment) { + throw new IllegalReferenceCountException(refCnt, increment); + } + if (refCntUpdater.compareAndSet(this, refCnt, refCnt + increment)) { + break; + } + } + return this; + } + + @Override + public final boolean release() { + for (;;) { + int refCnt = this.refCnt; + if (refCnt == 0) { + throw new IllegalReferenceCountException(0, -1); + } + + if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) { + if (refCnt == 1) { + deallocate(); + return true; + } + return false; + } + } + } + + @Override + public final boolean release(int decrement) { + if (decrement <= 0) { + throw new IllegalArgumentException("decrement: " + decrement + " (expected: > 0)"); + } + + for (;;) { + int refCnt = this.refCnt; + if (refCnt < decrement) { + throw new IllegalReferenceCountException(refCnt, -decrement); + } + + if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) { + if (refCnt == decrement) { + deallocate(); + return true; + } + return false; + } + } + } + + /** + * Called once {@link #refCnt()} is equals 0. + */ + protected abstract void deallocate(); +} diff --git a/common/src/common/net/buffer/AdvancedLeakAwareByteBuf.java b/common/src/common/net/buffer/AdvancedLeakAwareByteBuf.java new file mode 100644 index 0000000..2cd77b6 --- /dev/null +++ b/common/src/common/net/buffer/AdvancedLeakAwareByteBuf.java @@ -0,0 +1,724 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; + +import common.net.util.ResourceLeak; + +final class AdvancedLeakAwareByteBuf extends WrappedByteBuf { + + private final ResourceLeak leak; + + AdvancedLeakAwareByteBuf(ByteBuf buf, ResourceLeak leak) { + super(buf); + this.leak = leak; + } + + @Override + public boolean release() { + boolean deallocated = super.release(); + if (deallocated) { + leak.close(); + } else { + leak.record(); + } + return deallocated; + } + + @Override + public boolean release(int decrement) { + boolean deallocated = super.release(decrement); + if (deallocated) { + leak.close(); + } else { + leak.record(); + } + return deallocated; + } + + @Override + public ByteBuf order(ByteOrder endianness) { + leak.record(); + if (order() == endianness) { + return this; + } else { + return new AdvancedLeakAwareByteBuf(super.order(endianness), leak); + } + } + + @Override + public ByteBuf slice() { + leak.record(); + return new AdvancedLeakAwareByteBuf(super.slice(), leak); + } + + @Override + public ByteBuf slice(int index, int length) { + leak.record(); + return new AdvancedLeakAwareByteBuf(super.slice(index, length), leak); + } + + @Override + public ByteBuf duplicate() { + leak.record(); + return new AdvancedLeakAwareByteBuf(super.duplicate(), leak); + } + + @Override + public ByteBuf readSlice(int length) { + leak.record(); + return new AdvancedLeakAwareByteBuf(super.readSlice(length), leak); + } + + @Override + public ByteBuf discardReadBytes() { + leak.record(); + return super.discardReadBytes(); + } + + @Override + public ByteBuf discardSomeReadBytes() { + leak.record(); + return super.discardSomeReadBytes(); + } + + @Override + public ByteBuf ensureWritable(int minWritableBytes) { + leak.record(); + return super.ensureWritable(minWritableBytes); + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + leak.record(); + return super.ensureWritable(minWritableBytes, force); + } + + @Override + public boolean getBoolean(int index) { + leak.record(); + return super.getBoolean(index); + } + + @Override + public byte getByte(int index) { + leak.record(); + return super.getByte(index); + } + + @Override + public short getUnsignedByte(int index) { + leak.record(); + return super.getUnsignedByte(index); + } + + @Override + public short getShort(int index) { + leak.record(); + return super.getShort(index); + } + + @Override + public int getUnsignedShort(int index) { + leak.record(); + return super.getUnsignedShort(index); + } + + @Override + public int getMedium(int index) { + leak.record(); + return super.getMedium(index); + } + + @Override + public int getUnsignedMedium(int index) { + leak.record(); + return super.getUnsignedMedium(index); + } + + @Override + public int getInt(int index) { + leak.record(); + return super.getInt(index); + } + + @Override + public long getUnsignedInt(int index) { + leak.record(); + return super.getUnsignedInt(index); + } + + @Override + public long getLong(int index) { + leak.record(); + return super.getLong(index); + } + + @Override + public char getChar(int index) { + leak.record(); + return super.getChar(index); + } + + @Override + public float getFloat(int index) { + leak.record(); + return super.getFloat(index); + } + + @Override + public double getDouble(int index) { + leak.record(); + return super.getDouble(index); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst) { + leak.record(); + return super.getBytes(index, dst); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int length) { + leak.record(); + return super.getBytes(index, dst, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + leak.record(); + return super.getBytes(index, dst, dstIndex, length); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst) { + leak.record(); + return super.getBytes(index, dst); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + leak.record(); + return super.getBytes(index, dst, dstIndex, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + leak.record(); + return super.getBytes(index, dst); + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + leak.record(); + return super.getBytes(index, out, length); + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + leak.record(); + return super.getBytes(index, out, length); + } + + @Override + public ByteBuf setBoolean(int index, boolean value) { + leak.record(); + return super.setBoolean(index, value); + } + + @Override + public ByteBuf setByte(int index, int value) { + leak.record(); + return super.setByte(index, value); + } + + @Override + public ByteBuf setShort(int index, int value) { + leak.record(); + return super.setShort(index, value); + } + + @Override + public ByteBuf setMedium(int index, int value) { + leak.record(); + return super.setMedium(index, value); + } + + @Override + public ByteBuf setInt(int index, int value) { + leak.record(); + return super.setInt(index, value); + } + + @Override + public ByteBuf setLong(int index, long value) { + leak.record(); + return super.setLong(index, value); + } + + @Override + public ByteBuf setChar(int index, int value) { + leak.record(); + return super.setChar(index, value); + } + + @Override + public ByteBuf setFloat(int index, float value) { + leak.record(); + return super.setFloat(index, value); + } + + @Override + public ByteBuf setDouble(int index, double value) { + leak.record(); + return super.setDouble(index, value); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src) { + leak.record(); + return super.setBytes(index, src); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int length) { + leak.record(); + return super.setBytes(index, src, length); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + leak.record(); + return super.setBytes(index, src, srcIndex, length); + } + + @Override + public ByteBuf setBytes(int index, byte[] src) { + leak.record(); + return super.setBytes(index, src); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + leak.record(); + return super.setBytes(index, src, srcIndex, length); + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + leak.record(); + return super.setBytes(index, src); + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + leak.record(); + return super.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + leak.record(); + return super.setBytes(index, in, length); + } + + @Override + public ByteBuf setZero(int index, int length) { + leak.record(); + return super.setZero(index, length); + } + + @Override + public boolean readBoolean() { + leak.record(); + return super.readBoolean(); + } + + @Override + public byte readByte() { + leak.record(); + return super.readByte(); + } + + @Override + public short readUnsignedByte() { + leak.record(); + return super.readUnsignedByte(); + } + + @Override + public short readShort() { + leak.record(); + return super.readShort(); + } + + @Override + public int readUnsignedShort() { + leak.record(); + return super.readUnsignedShort(); + } + + @Override + public int readMedium() { + leak.record(); + return super.readMedium(); + } + + @Override + public int readUnsignedMedium() { + leak.record(); + return super.readUnsignedMedium(); + } + + @Override + public int readInt() { + leak.record(); + return super.readInt(); + } + + @Override + public long readUnsignedInt() { + leak.record(); + return super.readUnsignedInt(); + } + + @Override + public long readLong() { + leak.record(); + return super.readLong(); + } + + @Override + public char readChar() { + leak.record(); + return super.readChar(); + } + + @Override + public float readFloat() { + leak.record(); + return super.readFloat(); + } + + @Override + public double readDouble() { + leak.record(); + return super.readDouble(); + } + + @Override + public ByteBuf readBytes(int length) { + leak.record(); + return super.readBytes(length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst) { + leak.record(); + return super.readBytes(dst); + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int length) { + leak.record(); + return super.readBytes(dst, length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + leak.record(); + return super.readBytes(dst, dstIndex, length); + } + + @Override + public ByteBuf readBytes(byte[] dst) { + leak.record(); + return super.readBytes(dst); + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + leak.record(); + return super.readBytes(dst, dstIndex, length); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + leak.record(); + return super.readBytes(dst); + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + leak.record(); + return super.readBytes(out, length); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + leak.record(); + return super.readBytes(out, length); + } + + @Override + public ByteBuf skipBytes(int length) { + leak.record(); + return super.skipBytes(length); + } + + @Override + public ByteBuf writeBoolean(boolean value) { + leak.record(); + return super.writeBoolean(value); + } + + @Override + public ByteBuf writeByte(int value) { + leak.record(); + return super.writeByte(value); + } + + @Override + public ByteBuf writeShort(int value) { + leak.record(); + return super.writeShort(value); + } + + @Override + public ByteBuf writeMedium(int value) { + leak.record(); + return super.writeMedium(value); + } + + @Override + public ByteBuf writeInt(int value) { + leak.record(); + return super.writeInt(value); + } + + @Override + public ByteBuf writeLong(long value) { + leak.record(); + return super.writeLong(value); + } + + @Override + public ByteBuf writeChar(int value) { + leak.record(); + return super.writeChar(value); + } + + @Override + public ByteBuf writeFloat(float value) { + leak.record(); + return super.writeFloat(value); + } + + @Override + public ByteBuf writeDouble(double value) { + leak.record(); + return super.writeDouble(value); + } + + @Override + public ByteBuf writeBytes(ByteBuf src) { + leak.record(); + return super.writeBytes(src); + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int length) { + leak.record(); + return super.writeBytes(src, length); + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + leak.record(); + return super.writeBytes(src, srcIndex, length); + } + + @Override + public ByteBuf writeBytes(byte[] src) { + leak.record(); + return super.writeBytes(src); + } + + @Override + public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { + leak.record(); + return super.writeBytes(src, srcIndex, length); + } + + @Override + public ByteBuf writeBytes(ByteBuffer src) { + leak.record(); + return super.writeBytes(src); + } + + @Override + public int writeBytes(InputStream in, int length) throws IOException { + leak.record(); + return super.writeBytes(in, length); + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + leak.record(); + return super.writeBytes(in, length); + } + + @Override + public ByteBuf writeZero(int length) { + leak.record(); + return super.writeZero(length); + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + leak.record(); + return super.indexOf(fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + leak.record(); + return super.bytesBefore(value); + } + + @Override + public int bytesBefore(int length, byte value) { + leak.record(); + return super.bytesBefore(length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + leak.record(); + return super.bytesBefore(index, length, value); + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + leak.record(); + return super.forEachByte(processor); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + leak.record(); + return super.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + leak.record(); + return super.forEachByteDesc(processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + leak.record(); + return super.forEachByteDesc(index, length, processor); + } + + @Override + public ByteBuf copy() { + leak.record(); + return super.copy(); + } + + @Override + public ByteBuf copy(int index, int length) { + leak.record(); + return super.copy(index, length); + } + + @Override + public int nioBufferCount() { + leak.record(); + return super.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer() { + leak.record(); + return super.nioBuffer(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + leak.record(); + return super.nioBuffer(index, length); + } + + @Override + public ByteBuffer[] nioBuffers() { + leak.record(); + return super.nioBuffers(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + leak.record(); + return super.nioBuffers(index, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + leak.record(); + return super.internalNioBuffer(index, length); + } + + @Override + public String toString(Charset charset) { + leak.record(); + return super.toString(charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + leak.record(); + return super.toString(index, length, charset); + } + + @Override + public ByteBuf retain() { + leak.record(); + return super.retain(); + } + + @Override + public ByteBuf retain(int increment) { + leak.record(); + return super.retain(increment); + } + + @Override + public ByteBuf capacity(int newCapacity) { + leak.record(); + return super.capacity(newCapacity); + } +} diff --git a/common/src/common/net/buffer/ByteBuf.java b/common/src/common/net/buffer/ByteBuf.java new file mode 100644 index 0000000..664064e --- /dev/null +++ b/common/src/common/net/buffer/ByteBuf.java @@ -0,0 +1,1879 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; + +import common.net.util.ReferenceCounted; + +/** + * A random and sequential accessible sequence of zero or more bytes (octets). + * This interface provides an abstract view for one or more primitive byte + * arrays ({@code byte[]}) and {@linkplain ByteBuffer NIO buffers}. + * + *

Creation of a buffer

+ * + * It is recommended to create a new buffer using the helper methods in + * {@link Unpooled} rather than calling an individual implementation's + * constructor. + * + *

Random Access Indexing

+ * + * Just like an ordinary primitive byte array, {@link ByteBuf} uses + * zero-based indexing. + * It means the index of the first byte is always {@code 0} and the index of the last byte is + * always {@link #capacity() capacity - 1}. For example, to iterate all bytes of a buffer, you + * can do the following, regardless of its internal implementation: + * + *
+ * {@link ByteBuf} buffer = ...;
+ * for (int i = 0; i < buffer.capacity(); i ++) {
+ *     byte b = buffer.getByte(i);
+ *     System.out.println((char) b);
+ * }
+ * 
+ * + *

Sequential Access Indexing

+ * + * {@link ByteBuf} provides two pointer variables to support sequential + * read and write operations - {@link #readerIndex() readerIndex} for a read + * operation and {@link #writerIndex() writerIndex} for a write operation + * respectively. The following diagram shows how a buffer is segmented into + * three areas by the two pointers: + * + *
+ *      +-------------------+------------------+------------------+
+ *      | discardable bytes |  readable bytes  |  writable bytes  |
+ *      |                   |     (CONTENT)    |                  |
+ *      +-------------------+------------------+------------------+
+ *      |                   |                  |                  |
+ *      0      <=      readerIndex   <=   writerIndex    <=    capacity
+ * 
+ * + *

Readable bytes (the actual content)

+ * + * This segment is where the actual data is stored. Any operation whose name + * starts with {@code read} or {@code skip} will get or skip the data at the + * current {@link #readerIndex() readerIndex} and increase it by the number of + * read bytes. If the argument of the read operation is also a + * {@link ByteBuf} and no destination index is specified, the specified + * buffer's {@link #writerIndex() writerIndex} is increased together. + *

+ * If there's not enough content left, {@link IndexOutOfBoundsException} is + * raised. The default value of newly allocated, wrapped or copied buffer's + * {@link #readerIndex() readerIndex} is {@code 0}. + * + *

+ * // Iterates the readable bytes of a buffer.
+ * {@link ByteBuf} buffer = ...;
+ * while (buffer.readable()) {
+ *     System.out.println(buffer.readByte());
+ * }
+ * 
+ * + *

Writable bytes

+ * + * This segment is a undefined space which needs to be filled. Any operation + * whose name ends with {@code write} will write the data at the current + * {@link #writerIndex() writerIndex} and increase it by the number of written + * bytes. If the argument of the write operation is also a {@link ByteBuf}, + * and no source index is specified, the specified buffer's + * {@link #readerIndex() readerIndex} is increased together. + *

+ * If there's not enough writable bytes left, {@link IndexOutOfBoundsException} + * is raised. The default value of newly allocated buffer's + * {@link #writerIndex() writerIndex} is {@code 0}. The default value of + * wrapped or copied buffer's {@link #writerIndex() writerIndex} is the + * {@link #capacity() capacity} of the buffer. + * + *

+ * // Fills the writable bytes of a buffer with random integers.
+ * {@link ByteBuf} buffer = ...;
+ * while (buffer.maxWritableBytes() >= 4) {
+ *     buffer.writeInt(random.nextInt());
+ * }
+ * 
+ * + *

Discardable bytes

+ * + * This segment contains the bytes which were read already by a read operation. + * Initially, the size of this segment is {@code 0}, but its size increases up + * to the {@link #writerIndex() writerIndex} as read operations are executed. + * The read bytes can be discarded by calling {@link #discardReadBytes()} to + * reclaim unused area as depicted by the following diagram: + * + *
+ *  BEFORE discardReadBytes()
+ *
+ *      +-------------------+------------------+------------------+
+ *      | discardable bytes |  readable bytes  |  writable bytes  |
+ *      +-------------------+------------------+------------------+
+ *      |                   |                  |                  |
+ *      0      <=      readerIndex   <=   writerIndex    <=    capacity
+ *
+ *
+ *  AFTER discardReadBytes()
+ *
+ *      +------------------+--------------------------------------+
+ *      |  readable bytes  |    writable bytes (got more space)   |
+ *      +------------------+--------------------------------------+
+ *      |                  |                                      |
+ * readerIndex (0) <= writerIndex (decreased)        <=        capacity
+ * 
+ * + * Please note that there is no guarantee about the content of writable bytes + * after calling {@link #discardReadBytes()}. The writable bytes will not be + * moved in most cases and could even be filled with completely different data + * depending on the underlying buffer implementation. + * + *

Clearing the buffer indexes

+ * + * You can set both {@link #readerIndex() readerIndex} and + * {@link #writerIndex() writerIndex} to {@code 0} by calling {@link #clear()}. + * It does not clear the buffer content (e.g. filling with {@code 0}) but just + * clears the two pointers. Please also note that the semantic of this + * operation is different from {@link ByteBuffer#clear()}. + * + *
+ *  BEFORE clear()
+ *
+ *      +-------------------+------------------+------------------+
+ *      | discardable bytes |  readable bytes  |  writable bytes  |
+ *      +-------------------+------------------+------------------+
+ *      |                   |                  |                  |
+ *      0      <=      readerIndex   <=   writerIndex    <=    capacity
+ *
+ *
+ *  AFTER clear()
+ *
+ *      +---------------------------------------------------------+
+ *      |             writable bytes (got more space)             |
+ *      +---------------------------------------------------------+
+ *      |                                                         |
+ *      0 = readerIndex = writerIndex            <=            capacity
+ * 
+ * + *

Search operations

+ * + * For simple single-byte searches, use {@link #indexOf(int, int, byte)} and {@link #bytesBefore(int, int, byte)}. + * {@link #bytesBefore(byte)} is especially useful when you deal with a {@code NUL}-terminated string. + * For complicated searches, use {@link #forEachByte(int, int, ByteBufProcessor)} with a {@link ByteBufProcessor} + * implementation. + * + *

Mark and reset

+ * + * There are two marker indexes in every buffer. One is for storing + * {@link #readerIndex() readerIndex} and the other is for storing + * {@link #writerIndex() writerIndex}. You can always reposition one of the + * two indexes by calling a reset method. It works in a similar fashion to + * the mark and reset methods in {@link InputStream} except that there's no + * {@code readlimit}. + * + *

Derived buffers

+ * + * You can create a view of an existing buffer by calling either + * {@link #duplicate()}, {@link #slice()} or {@link #slice(int, int)}. + * A derived buffer will have an independent {@link #readerIndex() readerIndex}, + * {@link #writerIndex() writerIndex} and marker indexes, while it shares + * other internal data representation, just like a NIO buffer does. + *

+ * In case a completely fresh copy of an existing buffer is required, please + * call {@link #copy()} method instead. + * + *

Conversion to existing JDK types

+ * + *

Byte array

+ * + * If a {@link ByteBuf} is backed by a byte array (i.e. {@code byte[]}), + * you can access it directly via the {@link #array()} method. To determine + * if a buffer is backed by a byte array, {@link #hasArray()} should be used. + * + *

NIO Buffers

+ * + * If a {@link ByteBuf} can be converted into an NIO {@link ByteBuffer} which shares its + * content (i.e. view buffer), you can get it via the {@link #nioBuffer()} method. To determine + * if a buffer can be converted into an NIO buffer, use {@link #nioBufferCount()}. + * + *

Strings

+ * + * Various {@link #toString(Charset)} methods convert a {@link ByteBuf} + * into a {@link String}. Please note that {@link #toString()} is not a + * conversion method. + * + *

I/O Streams

+ * + * Please refer to {@link ByteBufInputStream} and + * {@link ByteBufOutputStream}. + */ + +public abstract class ByteBuf implements ReferenceCounted, Comparable { + + /** + * Returns the number of bytes (octets) this buffer can contain. + */ + public abstract int capacity(); + + /** + * Adjusts the capacity of this buffer. If the {@code newCapacity} is less than the current + * capacity, the content of this buffer is truncated. If the {@code newCapacity} is greater + * than the current capacity, the buffer is appended with unspecified data whose length is + * {@code (newCapacity - currentCapacity)}. + */ + public abstract ByteBuf capacity(int newCapacity); + + /** + * Returns the maximum allowed capacity of this buffer. If a user attempts to increase the + * capacity of this buffer beyond the maximum capacity using {@link #capacity(int)} or + * {@link #ensureWritable(int)}, those methods will raise an + * {@link IllegalArgumentException}. + */ + public abstract int maxCapacity(); + + /** + * Returns the {@link ByteBufAllocator} which created this buffer. + */ + public abstract ByteBufAllocator alloc(); + + /** + * Returns the endianness + * of this buffer. + */ + public abstract ByteOrder order(); + + /** + * Returns a buffer with the specified {@code endianness} which shares the whole region, + * indexes, and marks of this buffer. Modifying the content, the indexes, or the marks of the + * returned buffer or this buffer affects each other's content, indexes, and marks. If the + * specified {@code endianness} is identical to this buffer's byte order, this method can + * return {@code this}. This method does not modify {@code readerIndex} or {@code writerIndex} + * of this buffer. + */ + public abstract ByteBuf order(ByteOrder endianness); + + /** + * Return the underlying buffer instance if this buffer is a wrapper of another buffer. + * + * @return {@code null} if this buffer is not a wrapper + */ + public abstract ByteBuf unwrap(); + + /** + * Returns {@code true} if and only if this buffer is backed by an + * NIO direct buffer. + */ + public abstract boolean isDirect(); + + /** + * Returns the {@code readerIndex} of this buffer. + */ + public abstract int readerIndex(); + + /** + * Sets the {@code readerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code readerIndex} is + * less than {@code 0} or + * greater than {@code this.writerIndex} + */ + public abstract ByteBuf readerIndex(int readerIndex); + + /** + * Returns the {@code writerIndex} of this buffer. + */ + public abstract int writerIndex(); + + /** + * Sets the {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code writerIndex} is + * less than {@code this.readerIndex} or + * greater than {@code this.capacity} + */ + public abstract ByteBuf writerIndex(int writerIndex); + + /** + * Sets the {@code readerIndex} and {@code writerIndex} of this buffer + * in one shot. This method is useful when you have to worry about the + * invocation order of {@link #readerIndex(int)} and {@link #writerIndex(int)} + * methods. For example, the following code will fail: + * + *
+     * // Create a buffer whose readerIndex, writerIndex and capacity are
+     * // 0, 0 and 8 respectively.
+     * {@link ByteBuf} buf = {@link Unpooled}.buffer(8);
+     *
+     * // IndexOutOfBoundsException is thrown because the specified
+     * // readerIndex (2) cannot be greater than the current writerIndex (0).
+     * buf.readerIndex(2);
+     * buf.writerIndex(4);
+     * 
+ * + * The following code will also fail: + * + *
+     * // Create a buffer whose readerIndex, writerIndex and capacity are
+     * // 0, 8 and 8 respectively.
+     * {@link ByteBuf} buf = {@link Unpooled}.wrappedBuffer(new byte[8]);
+     *
+     * // readerIndex becomes 8.
+     * buf.readLong();
+     *
+     * // IndexOutOfBoundsException is thrown because the specified
+     * // writerIndex (4) cannot be less than the current readerIndex (8).
+     * buf.writerIndex(4);
+     * buf.readerIndex(2);
+     * 
+ * + * By contrast, this method guarantees that it never + * throws an {@link IndexOutOfBoundsException} as long as the specified + * indexes meet basic constraints, regardless what the current index + * values of the buffer are: + * + *
+     * // No matter what the current state of the buffer is, the following
+     * // call always succeeds as long as the capacity of the buffer is not
+     * // less than 4.
+     * buf.setIndex(2, 4);
+     * 
+ * + * @throws IndexOutOfBoundsException + * if the specified {@code readerIndex} is less than 0, + * if the specified {@code writerIndex} is less than the specified + * {@code readerIndex} or if the specified {@code writerIndex} is + * greater than {@code this.capacity} + */ + public abstract ByteBuf setIndex(int readerIndex, int writerIndex); + + /** + * Returns the number of readable bytes which is equal to + * {@code (this.writerIndex - this.readerIndex)}. + */ + public abstract int readableBytes(); + + /** + * Returns the number of writable bytes which is equal to + * {@code (this.capacity - this.writerIndex)}. + */ + public abstract int writableBytes(); + + /** + * Returns the maximum possible number of writable bytes, which is equal to + * {@code (this.maxCapacity - this.writerIndex)}. + */ + public abstract int maxWritableBytes(); + + /** + * Returns {@code true} + * if and only if {@code (this.writerIndex - this.readerIndex)} is greater + * than {@code 0}. + */ + public abstract boolean isReadable(); + + /** + * Returns {@code true} if and only if this buffer contains equal to or more than the specified number of elements. + */ + public abstract boolean isReadable(int size); + + /** + * Returns {@code true} + * if and only if {@code (this.capacity - this.writerIndex)} is greater + * than {@code 0}. + */ + public abstract boolean isWritable(); + + /** + * Returns {@code true} if and only if this buffer has enough room to allow writing the specified number of + * elements. + */ + public abstract boolean isWritable(int size); + + /** + * Sets the {@code readerIndex} and {@code writerIndex} of this buffer to + * {@code 0}. + * This method is identical to {@link #setIndex(int, int) setIndex(0, 0)}. + *

+ * Please note that the behavior of this method is different + * from that of NIO buffer, which sets the {@code limit} to + * the {@code capacity} of the buffer. + */ + public abstract ByteBuf clear(); + + /** + * Marks the current {@code readerIndex} in this buffer. You can + * reposition the current {@code readerIndex} to the marked + * {@code readerIndex} by calling {@link #resetReaderIndex()}. + * The initial value of the marked {@code readerIndex} is {@code 0}. + */ + public abstract ByteBuf markReaderIndex(); + + /** + * Repositions the current {@code readerIndex} to the marked + * {@code readerIndex} in this buffer. + * + * @throws IndexOutOfBoundsException + * if the current {@code writerIndex} is less than the marked + * {@code readerIndex} + */ + public abstract ByteBuf resetReaderIndex(); + + /** + * Marks the current {@code writerIndex} in this buffer. You can + * reposition the current {@code writerIndex} to the marked + * {@code writerIndex} by calling {@link #resetWriterIndex()}. + * The initial value of the marked {@code writerIndex} is {@code 0}. + */ + public abstract ByteBuf markWriterIndex(); + + /** + * Repositions the current {@code writerIndex} to the marked + * {@code writerIndex} in this buffer. + * + * @throws IndexOutOfBoundsException + * if the current {@code readerIndex} is greater than the marked + * {@code writerIndex} + */ + public abstract ByteBuf resetWriterIndex(); + + /** + * Discards the bytes between the 0th index and {@code readerIndex}. + * It moves the bytes between {@code readerIndex} and {@code writerIndex} + * to the 0th index, and sets {@code readerIndex} and {@code writerIndex} + * to {@code 0} and {@code oldWriterIndex - oldReaderIndex} respectively. + *

+ * Please refer to the class documentation for more detailed explanation. + */ + public abstract ByteBuf discardReadBytes(); + + /** + * Similar to {@link ByteBuf#discardReadBytes()} except that this method might discard + * some, all, or none of read bytes depending on its internal implementation to reduce + * overall memory bandwidth consumption at the cost of potentially additional memory + * consumption. + */ + public abstract ByteBuf discardSomeReadBytes(); + + /** + * Makes sure the number of {@linkplain #writableBytes() the writable bytes} + * is equal to or greater than the specified value. If there is enough + * writable bytes in this buffer, this method returns with no side effect. + * Otherwise, it raises an {@link IllegalArgumentException}. + * + * @param minWritableBytes + * the expected minimum number of writable bytes + * @throws IndexOutOfBoundsException + * if {@link #writerIndex()} + {@code minWritableBytes} > {@link #maxCapacity()} + */ + public abstract ByteBuf ensureWritable(int minWritableBytes); + + /** + * Tries to make sure the number of {@linkplain #writableBytes() the writable bytes} + * is equal to or greater than the specified value. Unlike {@link #ensureWritable(int)}, + * this method does not raise an exception but returns a code. + * + * @param minWritableBytes + * the expected minimum number of writable bytes + * @param force + * When {@link #writerIndex()} + {@code minWritableBytes} > {@link #maxCapacity()}: + *

    + *
  • {@code true} - the capacity of the buffer is expanded to {@link #maxCapacity()}
  • + *
  • {@code false} - the capacity of the buffer is unchanged
  • + *
+ * @return {@code 0} if the buffer has enough writable bytes, and its capacity is unchanged. + * {@code 1} if the buffer does not have enough bytes, and its capacity is unchanged. + * {@code 2} if the buffer has enough writable bytes, and its capacity has been increased. + * {@code 3} if the buffer does not have enough bytes, but its capacity has been + * increased to its maximum. + */ + public abstract int ensureWritable(int minWritableBytes, boolean force); + + /** + * Gets a boolean at the specified absolute (@code index) in this buffer. + * This method does not modify the {@code readerIndex} or {@code writerIndex} + * of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 1} is greater than {@code this.capacity} + */ + public abstract boolean getBoolean(int index); + + /** + * Gets a byte at the specified absolute {@code index} in this buffer. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 1} is greater than {@code this.capacity} + */ + public abstract byte getByte(int index); + + /** + * Gets an unsigned byte at the specified absolute {@code index} in this + * buffer. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 1} is greater than {@code this.capacity} + */ + public abstract short getUnsignedByte(int index); + + /** + * Gets a 16-bit short integer at the specified absolute {@code index} in + * this buffer. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 2} is greater than {@code this.capacity} + */ + public abstract short getShort(int index); + + /** + * Gets an unsigned 16-bit short integer at the specified absolute + * {@code index} in this buffer. This method does not modify + * {@code readerIndex} or {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 2} is greater than {@code this.capacity} + */ + public abstract int getUnsignedShort(int index); + + /** + * Gets a 24-bit medium integer at the specified absolute {@code index} in + * this buffer. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 3} is greater than {@code this.capacity} + */ + public abstract int getMedium(int index); + + /** + * Gets an unsigned 24-bit medium integer at the specified absolute + * {@code index} in this buffer. This method does not modify + * {@code readerIndex} or {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 3} is greater than {@code this.capacity} + */ + public abstract int getUnsignedMedium(int index); + + /** + * Gets a 32-bit integer at the specified absolute {@code index} in + * this buffer. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 4} is greater than {@code this.capacity} + */ + public abstract int getInt(int index); + + /** + * Gets an unsigned 32-bit integer at the specified absolute {@code index} + * in this buffer. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 4} is greater than {@code this.capacity} + */ + public abstract long getUnsignedInt(int index); + + /** + * Gets a 64-bit long integer at the specified absolute {@code index} in + * this buffer. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 8} is greater than {@code this.capacity} + */ + public abstract long getLong(int index); + + /** + * Gets a 2-byte UTF-16 character at the specified absolute + * {@code index} in this buffer. This method does not modify + * {@code readerIndex} or {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 2} is greater than {@code this.capacity} + */ + public abstract char getChar(int index); + + /** + * Gets a 32-bit floating point number at the specified absolute + * {@code index} in this buffer. This method does not modify + * {@code readerIndex} or {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 4} is greater than {@code this.capacity} + */ + public abstract float getFloat(int index); + + /** + * Gets a 64-bit floating point number at the specified absolute + * {@code index} in this buffer. This method does not modify + * {@code readerIndex} or {@code writerIndex} of this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 8} is greater than {@code this.capacity} + */ + public abstract double getDouble(int index); + + /** + * Transfers this buffer's data to the specified destination starting at + * the specified absolute {@code index} until the destination becomes + * non-writable. This method is basically same with + * {@link #getBytes(int, ByteBuf, int, int)}, except that this + * method increases the {@code writerIndex} of the destination by the + * number of the transferred bytes while + * {@link #getBytes(int, ByteBuf, int, int)} does not. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * the source buffer (i.e. {@code this}). + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + dst.writableBytes} is greater than + * {@code this.capacity} + */ + public abstract ByteBuf getBytes(int index, ByteBuf dst); + + /** + * Transfers this buffer's data to the specified destination starting at + * the specified absolute {@code index}. This method is basically same + * with {@link #getBytes(int, ByteBuf, int, int)}, except that this + * method increases the {@code writerIndex} of the destination by the + * number of the transferred bytes while + * {@link #getBytes(int, ByteBuf, int, int)} does not. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * the source buffer (i.e. {@code this}). + * + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0}, + * if {@code index + length} is greater than + * {@code this.capacity}, or + * if {@code length} is greater than {@code dst.writableBytes} + */ + public abstract ByteBuf getBytes(int index, ByteBuf dst, int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} + * of both the source (i.e. {@code this}) and the destination. + * + * @param dstIndex the first index of the destination + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0}, + * if the specified {@code dstIndex} is less than {@code 0}, + * if {@code index + length} is greater than + * {@code this.capacity}, or + * if {@code dstIndex + length} is greater than + * {@code dst.capacity} + */ + public abstract ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + dst.length} is greater than + * {@code this.capacity} + */ + public abstract ByteBuf getBytes(int index, byte[] dst); + + /** + * Transfers this buffer's data to the specified destination starting at + * the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} + * of this buffer. + * + * @param dstIndex the first index of the destination + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0}, + * if the specified {@code dstIndex} is less than {@code 0}, + * if {@code index + length} is greater than + * {@code this.capacity}, or + * if {@code dstIndex + length} is greater than + * {@code dst.length} + */ + public abstract ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the specified absolute {@code index} until the destination's position + * reaches its limit. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer while the destination's {@code position} will be increased. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + dst.remaining()} is greater than + * {@code this.capacity} + */ + public abstract ByteBuf getBytes(int index, ByteBuffer dst); + + /** + * Transfers this buffer's data to the specified stream starting at the + * specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + length} is greater than + * {@code this.capacity} + * @throws IOException + * if the specified stream threw an exception during I/O + */ + public abstract ByteBuf getBytes(int index, OutputStream out, int length) throws IOException; + + /** + * Transfers this buffer's data to the specified channel starting at the + * specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @param length the maximum number of bytes to transfer + * + * @return the actual number of bytes written out to the specified channel + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + length} is greater than + * {@code this.capacity} + * @throws IOException + * if the specified channel threw an exception during I/O + */ + public abstract int getBytes(int index, GatheringByteChannel out, int length) throws IOException; + + /** + * Sets the specified boolean at the specified absolute {@code index} in this + * buffer. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 1} is greater than {@code this.capacity} + */ + public abstract ByteBuf setBoolean(int index, boolean value); + + /** + * Sets the specified byte at the specified absolute {@code index} in this + * buffer. The 24 high-order bits of the specified value are ignored. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 1} is greater than {@code this.capacity} + */ + public abstract ByteBuf setByte(int index, int value); + + /** + * Sets the specified 16-bit short integer at the specified absolute + * {@code index} in this buffer. The 16 high-order bits of the specified + * value are ignored. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 2} is greater than {@code this.capacity} + */ + public abstract ByteBuf setShort(int index, int value); + + /** + * Sets the specified 24-bit medium integer at the specified absolute + * {@code index} in this buffer. Please note that the most significant + * byte is ignored in the specified value. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 3} is greater than {@code this.capacity} + */ + public abstract ByteBuf setMedium(int index, int value); + + /** + * Sets the specified 32-bit integer at the specified absolute + * {@code index} in this buffer. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 4} is greater than {@code this.capacity} + */ + public abstract ByteBuf setInt(int index, int value); + + /** + * Sets the specified 64-bit long integer at the specified absolute + * {@code index} in this buffer. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 8} is greater than {@code this.capacity} + */ + public abstract ByteBuf setLong(int index, long value); + + /** + * Sets the specified 2-byte UTF-16 character at the specified absolute + * {@code index} in this buffer. + * The 16 high-order bits of the specified value are ignored. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 2} is greater than {@code this.capacity} + */ + public abstract ByteBuf setChar(int index, int value); + + /** + * Sets the specified 32-bit floating-point number at the specified + * absolute {@code index} in this buffer. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 4} is greater than {@code this.capacity} + */ + public abstract ByteBuf setFloat(int index, float value); + + /** + * Sets the specified 64-bit floating-point number at the specified + * absolute {@code index} in this buffer. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * {@code index + 8} is greater than {@code this.capacity} + */ + public abstract ByteBuf setDouble(int index, double value); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the specified absolute {@code index} until the source buffer becomes + * unreadable. This method is basically same with + * {@link #setBytes(int, ByteBuf, int, int)}, except that this + * method increases the {@code readerIndex} of the source buffer by + * the number of the transferred bytes while + * {@link #setBytes(int, ByteBuf, int, int)} does not. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * the source buffer (i.e. {@code this}). + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + src.readableBytes} is greater than + * {@code this.capacity} + */ + public abstract ByteBuf setBytes(int index, ByteBuf src); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the specified absolute {@code index}. This method is basically same + * with {@link #setBytes(int, ByteBuf, int, int)}, except that this + * method increases the {@code readerIndex} of the source buffer by + * the number of the transferred bytes while + * {@link #setBytes(int, ByteBuf, int, int)} does not. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * the source buffer (i.e. {@code this}). + * + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0}, + * if {@code index + length} is greater than + * {@code this.capacity}, or + * if {@code length} is greater than {@code src.readableBytes} + */ + public abstract ByteBuf setBytes(int index, ByteBuf src, int length); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} + * of both the source (i.e. {@code this}) and the destination. + * + * @param srcIndex the first index of the source + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0}, + * if the specified {@code srcIndex} is less than {@code 0}, + * if {@code index + length} is greater than + * {@code this.capacity}, or + * if {@code srcIndex + length} is greater than + * {@code src.capacity} + */ + public abstract ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length); + + /** + * Transfers the specified source array's data to this buffer starting at + * the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + src.length} is greater than + * {@code this.capacity} + */ + public abstract ByteBuf setBytes(int index, byte[] src); + + /** + * Transfers the specified source array's data to this buffer starting at + * the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0}, + * if the specified {@code srcIndex} is less than {@code 0}, + * if {@code index + length} is greater than + * {@code this.capacity}, or + * if {@code srcIndex + length} is greater than {@code src.length} + */ + public abstract ByteBuf setBytes(int index, byte[] src, int srcIndex, int length); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the specified absolute {@code index} until the source buffer's position + * reaches its limit. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + src.remaining()} is greater than + * {@code this.capacity} + */ + public abstract ByteBuf setBytes(int index, ByteBuffer src); + + /** + * Transfers the content of the specified source stream to this buffer + * starting at the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @param length the number of bytes to transfer + * + * @return the actual number of bytes read in from the specified channel. + * {@code -1} if the specified channel is closed. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + length} is greater than {@code this.capacity} + * @throws IOException + * if the specified stream threw an exception during I/O + */ + public abstract int setBytes(int index, InputStream in, int length) throws IOException; + + /** + * Transfers the content of the specified source channel to this buffer + * starting at the specified absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @param length the maximum number of bytes to transfer + * + * @return the actual number of bytes read in from the specified channel. + * {@code -1} if the specified channel is closed. + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + length} is greater than {@code this.capacity} + * @throws IOException + * if the specified channel threw an exception during I/O + */ + public abstract int setBytes(int index, ScatteringByteChannel in, int length) throws IOException; + + /** + * Fills this buffer with NUL (0x00) starting at the specified + * absolute {@code index}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @param length the number of NULs to write to the buffer + * + * @throws IndexOutOfBoundsException + * if the specified {@code index} is less than {@code 0} or + * if {@code index + length} is greater than {@code this.capacity} + */ + public abstract ByteBuf setZero(int index, int length); + + /** + * Gets a boolean at the current {@code readerIndex} and increases + * the {@code readerIndex} by {@code 1} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 1} + */ + public abstract boolean readBoolean(); + + /** + * Gets a byte at the current {@code readerIndex} and increases + * the {@code readerIndex} by {@code 1} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 1} + */ + public abstract byte readByte(); + + /** + * Gets an unsigned byte at the current {@code readerIndex} and increases + * the {@code readerIndex} by {@code 1} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 1} + */ + public abstract short readUnsignedByte(); + + /** + * Gets a 16-bit short integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 2} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 2} + */ + public abstract short readShort(); + + /** + * Gets an unsigned 16-bit short integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 2} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 2} + */ + public abstract int readUnsignedShort(); + + /** + * Gets a 24-bit medium integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 3} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 3} + */ + public abstract int readMedium(); + + /** + * Gets an unsigned 24-bit medium integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 3} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 3} + */ + public abstract int readUnsignedMedium(); + + /** + * Gets a 32-bit integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 4} + */ + public abstract int readInt(); + + /** + * Gets an unsigned 32-bit integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 4} + */ + public abstract long readUnsignedInt(); + + /** + * Gets a 64-bit integer at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 8} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 8} + */ + public abstract long readLong(); + + /** + * Gets a 2-byte UTF-16 character at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 2} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 2} + */ + public abstract char readChar(); + + /** + * Gets a 32-bit floating point number at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 4} + */ + public abstract float readFloat(); + + /** + * Gets a 64-bit floating point number at the current {@code readerIndex} + * and increases the {@code readerIndex} by {@code 8} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.readableBytes} is less than {@code 8} + */ + public abstract double readDouble(); + + /** + * Transfers this buffer's data to a newly created buffer starting at + * the current {@code readerIndex} and increases the {@code readerIndex} + * by the number of the transferred bytes (= {@code length}). + * The returned buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0} and {@code length} respectively. + * + * @param length the number of bytes to transfer + * + * @return the newly created buffer which contains the transferred bytes + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} + */ + public abstract ByteBuf readBytes(int length); + + /** + * Returns a new slice of this buffer's sub-region starting at the current + * {@code readerIndex} and increases the {@code readerIndex} by the size + * of the new slice (= {@code length}). + * + * @param length the size of the new slice + * + * @return the newly created slice + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} + */ + public abstract ByteBuf readSlice(int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the current {@code readerIndex} until the destination becomes + * non-writable, and increases the {@code readerIndex} by the number of the + * transferred bytes. This method is basically same with + * {@link #readBytes(ByteBuf, int, int)}, except that this method + * increases the {@code writerIndex} of the destination by the number of + * the transferred bytes while {@link #readBytes(ByteBuf, int, int)} + * does not. + * + * @throws IndexOutOfBoundsException + * if {@code dst.writableBytes} is greater than + * {@code this.readableBytes} + */ + public abstract ByteBuf readBytes(ByteBuf dst); + + /** + * Transfers this buffer's data to the specified destination starting at + * the current {@code readerIndex} and increases the {@code readerIndex} + * by the number of the transferred bytes (= {@code length}). This method + * is basically same with {@link #readBytes(ByteBuf, int, int)}, + * except that this method increases the {@code writerIndex} of the + * destination by the number of the transferred bytes (= {@code length}) + * while {@link #readBytes(ByteBuf, int, int)} does not. + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} or + * if {@code length} is greater than {@code dst.writableBytes} + */ + public abstract ByteBuf readBytes(ByteBuf dst, int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the current {@code readerIndex} and increases the {@code readerIndex} + * by the number of the transferred bytes (= {@code length}). + * + * @param dstIndex the first index of the destination + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code dstIndex} is less than {@code 0}, + * if {@code length} is greater than {@code this.readableBytes}, or + * if {@code dstIndex + length} is greater than + * {@code dst.capacity} + */ + public abstract ByteBuf readBytes(ByteBuf dst, int dstIndex, int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the current {@code readerIndex} and increases the {@code readerIndex} + * by the number of the transferred bytes (= {@code dst.length}). + * + * @throws IndexOutOfBoundsException + * if {@code dst.length} is greater than {@code this.readableBytes} + */ + public abstract ByteBuf readBytes(byte[] dst); + + /** + * Transfers this buffer's data to the specified destination starting at + * the current {@code readerIndex} and increases the {@code readerIndex} + * by the number of the transferred bytes (= {@code length}). + * + * @param dstIndex the first index of the destination + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code dstIndex} is less than {@code 0}, + * if {@code length} is greater than {@code this.readableBytes}, or + * if {@code dstIndex + length} is greater than {@code dst.length} + */ + public abstract ByteBuf readBytes(byte[] dst, int dstIndex, int length); + + /** + * Transfers this buffer's data to the specified destination starting at + * the current {@code readerIndex} until the destination's position + * reaches its limit, and increases the {@code readerIndex} by the + * number of the transferred bytes. + * + * @throws IndexOutOfBoundsException + * if {@code dst.remaining()} is greater than + * {@code this.readableBytes} + */ + public abstract ByteBuf readBytes(ByteBuffer dst); + + /** + * Transfers this buffer's data to the specified stream starting at the + * current {@code readerIndex}. + * + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} + * @throws IOException + * if the specified stream threw an exception during I/O + */ + public abstract ByteBuf readBytes(OutputStream out, int length) throws IOException; + + /** + * Transfers this buffer's data to the specified stream starting at the + * current {@code readerIndex}. + * + * @param length the maximum number of bytes to transfer + * + * @return the actual number of bytes written out to the specified channel + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} + * @throws IOException + * if the specified channel threw an exception during I/O + */ + public abstract int readBytes(GatheringByteChannel out, int length) throws IOException; + + /** + * Increases the current {@code readerIndex} by the specified + * {@code length} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} + */ + public abstract ByteBuf skipBytes(int length); + + /** + * Sets the specified boolean at the current {@code writerIndex} + * and increases the {@code writerIndex} by {@code 1} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 1} + */ + public abstract ByteBuf writeBoolean(boolean value); + + /** + * Sets the specified byte at the current {@code writerIndex} + * and increases the {@code writerIndex} by {@code 1} in this buffer. + * The 24 high-order bits of the specified value are ignored. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 1} + */ + public abstract ByteBuf writeByte(int value); + + /** + * Sets the specified 16-bit short integer at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 2} + * in this buffer. The 16 high-order bits of the specified value are ignored. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 2} + */ + public abstract ByteBuf writeShort(int value); + + /** + * Sets the specified 24-bit medium integer at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 3} + * in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 3} + */ + public abstract ByteBuf writeMedium(int value); + + /** + * Sets the specified 32-bit integer at the current {@code writerIndex} + * and increases the {@code writerIndex} by {@code 4} in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 4} + */ + public abstract ByteBuf writeInt(int value); + + /** + * Sets the specified 64-bit long integer at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 8} + * in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 8} + */ + public abstract ByteBuf writeLong(long value); + + /** + * Sets the specified 2-byte UTF-16 character at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 2} + * in this buffer. The 16 high-order bits of the specified value are ignored. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 2} + */ + public abstract ByteBuf writeChar(int value); + + /** + * Sets the specified 32-bit floating point number at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 4} + * in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 4} + */ + public abstract ByteBuf writeFloat(float value); + + /** + * Sets the specified 64-bit floating point number at the current + * {@code writerIndex} and increases the {@code writerIndex} by {@code 8} + * in this buffer. + * + * @throws IndexOutOfBoundsException + * if {@code this.writableBytes} is less than {@code 8} + */ + public abstract ByteBuf writeDouble(double value); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the current {@code writerIndex} until the source buffer becomes + * unreadable, and increases the {@code writerIndex} by the number of + * the transferred bytes. This method is basically same with + * {@link #writeBytes(ByteBuf, int, int)}, except that this method + * increases the {@code readerIndex} of the source buffer by the number of + * the transferred bytes while {@link #writeBytes(ByteBuf, int, int)} + * does not. + * + * @throws IndexOutOfBoundsException + * if {@code src.readableBytes} is greater than + * {@code this.writableBytes} + */ + public abstract ByteBuf writeBytes(ByteBuf src); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the current {@code writerIndex} and increases the {@code writerIndex} + * by the number of the transferred bytes (= {@code length}). This method + * is basically same with {@link #writeBytes(ByteBuf, int, int)}, + * except that this method increases the {@code readerIndex} of the source + * buffer by the number of the transferred bytes (= {@code length}) while + * {@link #writeBytes(ByteBuf, int, int)} does not. + * + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.writableBytes} or + * if {@code length} is greater then {@code src.readableBytes} + */ + public abstract ByteBuf writeBytes(ByteBuf src, int length); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the current {@code writerIndex} and increases the {@code writerIndex} + * by the number of the transferred bytes (= {@code length}). + * + * @param srcIndex the first index of the source + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code srcIndex} is less than {@code 0}, + * if {@code srcIndex + length} is greater than + * {@code src.capacity}, or + * if {@code length} is greater than {@code this.writableBytes} + */ + public abstract ByteBuf writeBytes(ByteBuf src, int srcIndex, int length); + + /** + * Transfers the specified source array's data to this buffer starting at + * the current {@code writerIndex} and increases the {@code writerIndex} + * by the number of the transferred bytes (= {@code src.length}). + * + * @throws IndexOutOfBoundsException + * if {@code src.length} is greater than {@code this.writableBytes} + */ + public abstract ByteBuf writeBytes(byte[] src); + + /** + * Transfers the specified source array's data to this buffer starting at + * the current {@code writerIndex} and increases the {@code writerIndex} + * by the number of the transferred bytes (= {@code length}). + * + * @param srcIndex the first index of the source + * @param length the number of bytes to transfer + * + * @throws IndexOutOfBoundsException + * if the specified {@code srcIndex} is less than {@code 0}, + * if {@code srcIndex + length} is greater than + * {@code src.length}, or + * if {@code length} is greater than {@code this.writableBytes} + */ + public abstract ByteBuf writeBytes(byte[] src, int srcIndex, int length); + + /** + * Transfers the specified source buffer's data to this buffer starting at + * the current {@code writerIndex} until the source buffer's position + * reaches its limit, and increases the {@code writerIndex} by the + * number of the transferred bytes. + * + * @throws IndexOutOfBoundsException + * if {@code src.remaining()} is greater than + * {@code this.writableBytes} + */ + public abstract ByteBuf writeBytes(ByteBuffer src); + + /** + * Transfers the content of the specified stream to this buffer + * starting at the current {@code writerIndex} and increases the + * {@code writerIndex} by the number of the transferred bytes. + * + * @param length the number of bytes to transfer + * + * @return the actual number of bytes read in from the specified stream + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.writableBytes} + * @throws IOException + * if the specified stream threw an exception during I/O + */ + public abstract int writeBytes(InputStream in, int length) throws IOException; + + /** + * Transfers the content of the specified channel to this buffer + * starting at the current {@code writerIndex} and increases the + * {@code writerIndex} by the number of the transferred bytes. + * + * @param length the maximum number of bytes to transfer + * + * @return the actual number of bytes read in from the specified channel + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.writableBytes} + * @throws IOException + * if the specified channel threw an exception during I/O + */ + public abstract int writeBytes(ScatteringByteChannel in, int length) throws IOException; + + /** + * Fills this buffer with NUL (0x00) starting at the current + * {@code writerIndex} and increases the {@code writerIndex} by the + * specified {@code length}. + * + * @param length the number of NULs to write to the buffer + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.writableBytes} + */ + public abstract ByteBuf writeZero(int length); + + /** + * Locates the first occurrence of the specified {@code value} in this + * buffer. The search takes place from the specified {@code fromIndex} + * (inclusive) to the specified {@code toIndex} (exclusive). + *

+ * If {@code fromIndex} is greater than {@code toIndex}, the search is + * performed in a reversed order. + *

+ * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @return the absolute index of the first occurrence if found. + * {@code -1} otherwise. + */ + public abstract int indexOf(int fromIndex, int toIndex, byte value); + + /** + * Locates the first occurrence of the specified {@code value} in this + * buffer. The search takes place from the current {@code readerIndex} + * (inclusive) to the current {@code writerIndex} (exclusive). + *

+ * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @return the number of bytes between the current {@code readerIndex} + * and the first occurrence if found. {@code -1} otherwise. + */ + public abstract int bytesBefore(byte value); + + /** + * Locates the first occurrence of the specified {@code value} in this + * buffer. The search starts from the current {@code readerIndex} + * (inclusive) and lasts for the specified {@code length}. + *

+ * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @return the number of bytes between the current {@code readerIndex} + * and the first occurrence if found. {@code -1} otherwise. + * + * @throws IndexOutOfBoundsException + * if {@code length} is greater than {@code this.readableBytes} + */ + public abstract int bytesBefore(int length, byte value); + + /** + * Locates the first occurrence of the specified {@code value} in this + * buffer. The search starts from the specified {@code index} (inclusive) + * and lasts for the specified {@code length}. + *

+ * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @return the number of bytes between the specified {@code index} + * and the first occurrence if found. {@code -1} otherwise. + * + * @throws IndexOutOfBoundsException + * if {@code index + length} is greater than {@code this.capacity} + */ + public abstract int bytesBefore(int index, int length, byte value); + + /** + * Iterates over the readable bytes of this buffer with the specified {@code processor} in ascending order. + * + * @return {@code -1} if the processor iterated to or beyond the end of the readable bytes. + * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}. + */ + public abstract int forEachByte(ByteBufProcessor processor); + + /** + * Iterates over the specified area of this buffer with the specified {@code processor} in ascending order. + * (i.e. {@code index}, {@code (index + 1)}, .. {@code (index + length - 1)}) + * + * @return {@code -1} if the processor iterated to or beyond the end of the specified area. + * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}. + */ + public abstract int forEachByte(int index, int length, ByteBufProcessor processor); + + /** + * Iterates over the readable bytes of this buffer with the specified {@code processor} in descending order. + * + * @return {@code -1} if the processor iterated to or beyond the beginning of the readable bytes. + * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}. + */ + public abstract int forEachByteDesc(ByteBufProcessor processor); + + /** + * Iterates over the specified area of this buffer with the specified {@code processor} in descending order. + * (i.e. {@code (index + length - 1)}, {@code (index + length - 2)}, ... {@code index}) + * + * + * @return {@code -1} if the processor iterated to or beyond the beginning of the specified area. + * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}. + */ + public abstract int forEachByteDesc(int index, int length, ByteBufProcessor processor); + + /** + * Returns a copy of this buffer's readable bytes. Modifying the content + * of the returned buffer or this buffer does not affect each other at all. + * This method is identical to {@code buf.copy(buf.readerIndex(), buf.readableBytes())}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + */ + public abstract ByteBuf copy(); + + /** + * Returns a copy of this buffer's sub-region. Modifying the content of + * the returned buffer or this buffer does not affect each other at all. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + */ + public abstract ByteBuf copy(int index, int length); + + /** + * Returns a slice of this buffer's readable bytes. Modifying the content + * of the returned buffer or this buffer affects each other's content + * while they maintain separate indexes and marks. This method is + * identical to {@code buf.slice(buf.readerIndex(), buf.readableBytes())}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + */ + public abstract ByteBuf slice(); + + /** + * Returns a slice of this buffer's sub-region. Modifying the content of + * the returned buffer or this buffer affects each other's content while + * they maintain separate indexes and marks. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + */ + public abstract ByteBuf slice(int index, int length); + + /** + * Returns a buffer which shares the whole region of this buffer. + * Modifying the content of the returned buffer or this buffer affects + * each other's content while they maintain separate indexes and marks. + * This method is identical to {@code buf.slice(0, buf.capacity())}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + */ + public abstract ByteBuf duplicate(); + + /** + * Returns the maximum number of NIO {@link ByteBuffer}s that consist this buffer. Note that {@link #nioBuffers()} + * or {@link #nioBuffers(int, int)} might return a less number of {@link ByteBuffer}s. + * + * @return {@code -1} if this buffer has no underlying {@link ByteBuffer}. + * the number of the underlying {@link ByteBuffer}s if this buffer has at least one underlying + * {@link ByteBuffer}. Note that this method does not return {@code 0} to avoid confusion. + * + * @see #nioBuffer() + * @see #nioBuffer(int, int) + * @see #nioBuffers() + * @see #nioBuffers(int, int) + */ + public abstract int nioBufferCount(); + + /** + * Exposes this buffer's readable bytes as an NIO {@link ByteBuffer}. The returned buffer + * shares the content with this buffer, while changing the position and limit of the returned + * NIO buffer does not affect the indexes and marks of this buffer. This method is identical + * to {@code buf.nioBuffer(buf.readerIndex(), buf.readableBytes())}. This method does not + * modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the + * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic + * buffer and it adjusted its capacity. + * + * @throws UnsupportedOperationException + * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself + * + * @see #nioBufferCount() + * @see #nioBuffers() + * @see #nioBuffers(int, int) + */ + public abstract ByteBuffer nioBuffer(); + + /** + * Exposes this buffer's sub-region as an NIO {@link ByteBuffer}. The returned buffer + * shares the content with this buffer, while changing the position and limit of the returned + * NIO buffer does not affect the indexes and marks of this buffer. This method does not + * modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the + * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic + * buffer and it adjusted its capacity. + * + * @throws UnsupportedOperationException + * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself + * + * @see #nioBufferCount() + * @see #nioBuffers() + * @see #nioBuffers(int, int) + */ + public abstract ByteBuffer nioBuffer(int index, int length); + + /** + * Internal use only: Exposes the internal NIO buffer. + */ + public abstract ByteBuffer internalNioBuffer(int index, int length); + + /** + * Exposes this buffer's readable bytes as an NIO {@link ByteBuffer}'s. The returned buffer + * shares the content with this buffer, while changing the position and limit of the returned + * NIO buffer does not affect the indexes and marks of this buffer. This method does not + * modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the + * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic + * buffer and it adjusted its capacity. + * + * + * @throws UnsupportedOperationException + * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself + * + * @see #nioBufferCount() + * @see #nioBuffer() + * @see #nioBuffer(int, int) + */ + public abstract ByteBuffer[] nioBuffers(); + + /** + * Exposes this buffer's bytes as an NIO {@link ByteBuffer}'s for the specified index and length + * The returned buffer shares the content with this buffer, while changing the position and limit + * of the returned NIO buffer does not affect the indexes and marks of this buffer. This method does + * not modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the + * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic + * buffer and it adjusted its capacity. + * + * @throws UnsupportedOperationException + * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself + * + * @see #nioBufferCount() + * @see #nioBuffer() + * @see #nioBuffer(int, int) + */ + public abstract ByteBuffer[] nioBuffers(int index, int length); + + /** + * Returns {@code true} if and only if this buffer has a backing byte array. + * If this method returns true, you can safely call {@link #array()} and + * {@link #arrayOffset()}. + */ + public abstract boolean hasArray(); + + /** + * Returns the backing byte array of this buffer. + * + * @throws UnsupportedOperationException + * if there no accessible backing byte array + */ + public abstract byte[] array(); + + /** + * Returns the offset of the first byte within the backing byte array of + * this buffer. + * + * @throws UnsupportedOperationException + * if there no accessible backing byte array + */ + public abstract int arrayOffset(); + + /** + * Returns {@code true} if and only if this buffer has a reference to the low-level memory address that points + * to the backing data. + */ + public abstract boolean hasMemoryAddress(); + + /** + * Returns the low-level memory address that point to the first byte of ths backing data. + * + * @throws UnsupportedOperationException + * if this buffer does not support accessing the low-level memory address + */ + public abstract long memoryAddress(); + + /** + * Decodes this buffer's readable bytes into a string with the specified + * character set name. This method is identical to + * {@code buf.toString(buf.readerIndex(), buf.readableBytes(), charsetName)}. + * This method does not modify {@code readerIndex} or {@code writerIndex} of + * this buffer. + * + * @throws UnsupportedCharsetException + * if the specified character set name is not supported by the + * current VM + */ + public abstract String toString(Charset charset); + + /** + * Decodes this buffer's sub-region into a string with the specified + * character set. This method does not modify {@code readerIndex} or + * {@code writerIndex} of this buffer. + */ + public abstract String toString(int index, int length, Charset charset); + + /** + * Returns a hash code which was calculated from the content of this + * buffer. If there's a byte array which is + * {@linkplain #equals(Object) equal to} this array, both arrays should + * return the same value. + */ + @Override + public abstract int hashCode(); + + /** + * Determines if the content of the specified buffer is identical to the + * content of this array. 'Identical' here means: + *

    + *
  • the size of the contents of the two buffers are same and
  • + *
  • every single byte of the content of the two buffers are same.
  • + *
+ * Please note that it does not compare {@link #readerIndex()} nor + * {@link #writerIndex()}. This method also returns {@code false} for + * {@code null} and an object which is not an instance of + * {@link ByteBuf} type. + */ + @Override + public abstract boolean equals(Object obj); + + /** + * Compares the content of the specified buffer to the content of this + * buffer. Comparison is performed in the same manner with the string + * comparison functions of various languages such as {@code strcmp}, + * {@code memcmp} and {@link String#compareTo(String)}. + */ + @Override + public abstract int compareTo(ByteBuf buffer); + + /** + * Returns the string representation of this buffer. This method does not + * necessarily return the whole content of the buffer but returns + * the values of the key properties such as {@link #readerIndex()}, + * {@link #writerIndex()} and {@link #capacity()}. + */ + @Override + public abstract String toString(); + + @Override + public abstract ByteBuf retain(int increment); + + @Override + public abstract ByteBuf retain(); +} diff --git a/common/src/common/net/buffer/ByteBufAllocator.java b/common/src/common/net/buffer/ByteBufAllocator.java new file mode 100644 index 0000000..045f669 --- /dev/null +++ b/common/src/common/net/buffer/ByteBufAllocator.java @@ -0,0 +1,128 @@ +/* + * 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 common.net.buffer; + +/** + * Implementations are responsible to allocate buffers. Implementations of this interface are expected to be + * thread-safe. + */ +public interface ByteBufAllocator { + + ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR; + + /** + * Allocate a {@link ByteBuf}. If it is a direct or heap buffer + * depends on the actual implementation. + */ + ByteBuf buffer(); + + /** + * Allocate a {@link ByteBuf} with the given initial capacity. + * If it is a direct or heap buffer depends on the actual implementation. + */ + ByteBuf buffer(int initialCapacity); + + /** + * Allocate a {@link ByteBuf} with the given initial capacity and the given + * maximal capacity. If it is a direct or heap buffer depends on the actual + * implementation. + */ + ByteBuf buffer(int initialCapacity, int maxCapacity); + + /** + * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O. + */ + ByteBuf ioBuffer(); + + /** + * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O. + */ + ByteBuf ioBuffer(int initialCapacity); + + /** + * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O. + */ + ByteBuf ioBuffer(int initialCapacity, int maxCapacity); + + /** + * Allocate a heap {@link ByteBuf}. + */ + ByteBuf heapBuffer(); + + /** + * Allocate a heap {@link ByteBuf} with the given initial capacity. + */ + ByteBuf heapBuffer(int initialCapacity); + + /** + * Allocate a heap {@link ByteBuf} with the given initial capacity and the given + * maximal capacity. + */ + ByteBuf heapBuffer(int initialCapacity, int maxCapacity); + + /** + * Allocate a direct {@link ByteBuf}. + */ + ByteBuf directBuffer(); + + /** + * Allocate a direct {@link ByteBuf} with the given initial capacity. + */ + ByteBuf directBuffer(int initialCapacity); + + /** + * Allocate a direct {@link ByteBuf} with the given initial capacity and the given + * maximal capacity. + */ + ByteBuf directBuffer(int initialCapacity, int maxCapacity); + + /** + * Allocate a {@link CompositeByteBuf}. + * If it is a direct or heap buffer depends on the actual implementation. + */ + CompositeByteBuf compositeBuffer(); + + /** + * Allocate a {@link CompositeByteBuf} with the given maximum number of components that can be stored in it. + * If it is a direct or heap buffer depends on the actual implementation. + */ + CompositeByteBuf compositeBuffer(int maxNumComponents); + + /** + * Allocate a heap {@link CompositeByteBuf}. + */ + CompositeByteBuf compositeHeapBuffer(); + + /** + * Allocate a heap {@link CompositeByteBuf} with the given maximum number of components that can be stored in it. + */ + CompositeByteBuf compositeHeapBuffer(int maxNumComponents); + + /** + * Allocate a direct {@link CompositeByteBuf}. + */ + CompositeByteBuf compositeDirectBuffer(); + + /** + * Allocate a direct {@link CompositeByteBuf} with the given maximum number of components that can be stored in it. + */ + CompositeByteBuf compositeDirectBuffer(int maxNumComponents); + + /** + * Returns {@code true} if direct {@link ByteBuf}'s are pooled + */ + boolean isDirectBufferPooled(); +} diff --git a/common/src/common/net/buffer/ByteBufInputStream.java b/common/src/common/net/buffer/ByteBufInputStream.java new file mode 100644 index 0000000..70a0925 --- /dev/null +++ b/common/src/common/net/buffer/ByteBufInputStream.java @@ -0,0 +1,257 @@ +/* + * 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 common.net.buffer; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@link InputStream} which reads data from a {@link ByteBuf}. + *

+ * A read operation against this stream will occur at the {@code readerIndex} + * of its underlying buffer and the {@code readerIndex} will increase during + * the read operation. Please note that it only reads up to the number of + * readable bytes determined at the moment of construction. Therefore, + * updating {@link ByteBuf#writerIndex()} will not affect the return + * value of {@link #available()}. + *

+ * This stream implements {@link DataInput} for your convenience. + * The endianness of the stream is not always big endian but depends on + * the endianness of the underlying buffer. + * + * @see ByteBufOutputStream + */ +public class ByteBufInputStream extends InputStream implements DataInput { + + private final ByteBuf buffer; + private final int startIndex; + private final int endIndex; + + /** + * Creates a new stream which reads data from the specified {@code buffer} + * starting at the current {@code readerIndex} and ending at the current + * {@code writerIndex}. + */ + public ByteBufInputStream(ByteBuf buffer) { + this(buffer, buffer.readableBytes()); + } + + /** + * Creates a new stream which reads data from the specified {@code buffer} + * starting at the current {@code readerIndex} and ending at + * {@code readerIndex + length}. + * + * @throws IndexOutOfBoundsException + * if {@code readerIndex + length} is greater than + * {@code writerIndex} + */ + public ByteBufInputStream(ByteBuf buffer, int length) { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + if (length < 0) { + throw new IllegalArgumentException("length: " + length); + } + if (length > buffer.readableBytes()) { + throw new IndexOutOfBoundsException("Too many bytes to be read - Needs " + + length + ", maximum is " + buffer.readableBytes()); + } + + this.buffer = buffer; + startIndex = buffer.readerIndex(); + endIndex = startIndex + length; + buffer.markReaderIndex(); + } + + /** + * Returns the number of read bytes by this stream so far. + */ + public int readBytes() { + return buffer.readerIndex() - startIndex; + } + + @Override + public int available() throws IOException { + return endIndex - buffer.readerIndex(); + } + + @Override + public void mark(int readlimit) { + buffer.markReaderIndex(); + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public int read() throws IOException { + if (!buffer.isReadable()) { + return -1; + } + return buffer.readByte() & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int available = available(); + if (available == 0) { + return -1; + } + + len = Math.min(available, len); + buffer.readBytes(b, off, len); + return len; + } + + @Override + public void reset() throws IOException { + buffer.resetReaderIndex(); + } + + @Override + public long skip(long n) throws IOException { + if (n > Integer.MAX_VALUE) { + return skipBytes(Integer.MAX_VALUE); + } else { + return skipBytes((int) n); + } + } + + @Override + public boolean readBoolean() throws IOException { + checkAvailable(1); + return read() != 0; + } + + @Override + public byte readByte() throws IOException { + if (!buffer.isReadable()) { + throw new EOFException(); + } + return buffer.readByte(); + } + + @Override + public char readChar() throws IOException { + return (char) readShort(); + } + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + @Override + public void readFully(byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + @Override + public void readFully(byte[] b, int off, int len) throws IOException { + checkAvailable(len); + buffer.readBytes(b, off, len); + } + + @Override + public int readInt() throws IOException { + checkAvailable(4); + return buffer.readInt(); + } + + private final StringBuilder lineBuf = new StringBuilder(); + + @Override + public String readLine() throws IOException { + lineBuf.setLength(0); + + loop: while (true) { + if (!buffer.isReadable()) { + return lineBuf.length() > 0 ? lineBuf.toString() : null; + } + + int c = buffer.readUnsignedByte(); + switch (c) { + case '\n': + break loop; + + case '\r': + if (buffer.isReadable() && (char) buffer.getUnsignedByte(buffer.readerIndex()) == '\n') { + buffer.skipBytes(1); + } + break loop; + + default: + lineBuf.append((char) c); + } + } + + return lineBuf.toString(); + } + + @Override + public long readLong() throws IOException { + checkAvailable(8); + return buffer.readLong(); + } + + @Override + public short readShort() throws IOException { + checkAvailable(2); + return buffer.readShort(); + } + + @Override + public String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + @Override + public int readUnsignedByte() throws IOException { + return readByte() & 0xff; + } + + @Override + public int readUnsignedShort() throws IOException { + return readShort() & 0xffff; + } + + @Override + public int skipBytes(int n) throws IOException { + int nBytes = Math.min(available(), n); + buffer.skipBytes(nBytes); + return nBytes; + } + + private void checkAvailable(int fieldSize) throws IOException { + if (fieldSize < 0) { + throw new IndexOutOfBoundsException("fieldSize cannot be a negative number"); + } + if (fieldSize > available()) { + throw new EOFException("fieldSize is too long! Length is " + fieldSize + + ", but maximum is " + available()); + } + } +} diff --git a/common/src/common/net/buffer/ByteBufOutputStream.java b/common/src/common/net/buffer/ByteBufOutputStream.java new file mode 100644 index 0000000..81af491 --- /dev/null +++ b/common/src/common/net/buffer/ByteBufOutputStream.java @@ -0,0 +1,146 @@ +/* + * 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 common.net.buffer; + +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An {@link OutputStream} which writes data to a {@link ByteBuf}. + *

+ * A write operation against this stream will occur at the {@code writerIndex} + * of its underlying buffer and the {@code writerIndex} will increase during + * the write operation. + *

+ * This stream implements {@link DataOutput} for your convenience. + * The endianness of the stream is not always big endian but depends on + * the endianness of the underlying buffer. + * + * @see ByteBufInputStream + */ +public class ByteBufOutputStream extends OutputStream implements DataOutput { + + private final ByteBuf buffer; + private final int startIndex; + private final DataOutputStream utf8out = new DataOutputStream(this); + + /** + * Creates a new stream which writes data to the specified {@code buffer}. + */ + public ByteBufOutputStream(ByteBuf buffer) { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + this.buffer = buffer; + startIndex = buffer.writerIndex(); + } + + /** + * Returns the number of written bytes by this stream so far. + */ + public int writtenBytes() { + return buffer.writerIndex() - startIndex; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return; + } + + buffer.writeBytes(b, off, len); + } + + @Override + public void write(byte[] b) throws IOException { + buffer.writeBytes(b); + } + + @Override + public void write(int b) throws IOException { + buffer.writeByte((byte) b); + } + + @Override + public void writeBoolean(boolean v) throws IOException { + write(v? (byte) 1 : (byte) 0); + } + + @Override + public void writeByte(int v) throws IOException { + write(v); + } + + @Override + public void writeBytes(String s) throws IOException { + int len = s.length(); + for (int i = 0; i < len; i ++) { + write((byte) s.charAt(i)); + } + } + + @Override + public void writeChar(int v) throws IOException { + writeShort((short) v); + } + + @Override + public void writeChars(String s) throws IOException { + int len = s.length(); + for (int i = 0 ; i < len ; i ++) { + writeChar(s.charAt(i)); + } + } + + @Override + public void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + @Override + public void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + @Override + public void writeInt(int v) throws IOException { + buffer.writeInt(v); + } + + @Override + public void writeLong(long v) throws IOException { + buffer.writeLong(v); + } + + @Override + public void writeShort(int v) throws IOException { + buffer.writeShort((short) v); + } + + @Override + public void writeUTF(String s) throws IOException { + utf8out.writeUTF(s); + } + + /** + * Returns the buffer where this stream is writing data. + */ + public ByteBuf buffer() { + return buffer; + } +} diff --git a/common/src/common/net/buffer/ByteBufProcessor.java b/common/src/common/net/buffer/ByteBufProcessor.java new file mode 100644 index 0000000..67f4ad4 --- /dev/null +++ b/common/src/common/net/buffer/ByteBufProcessor.java @@ -0,0 +1,126 @@ +/* + * Copyright 2013 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 common.net.buffer; + +public interface ByteBufProcessor { + + /** + * Aborts on a {@code NUL (0x00)}. + */ + ByteBufProcessor FIND_NUL = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value != 0; + } + }; + + /** + * Aborts on a non-{@code NUL (0x00)}. + */ + ByteBufProcessor FIND_NON_NUL = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value == 0; + } + }; + + /** + * Aborts on a {@code CR ('\r')}. + */ + ByteBufProcessor FIND_CR = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value != '\r'; + } + }; + + /** + * Aborts on a non-{@code CR ('\r')}. + */ + ByteBufProcessor FIND_NON_CR = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value == '\r'; + } + }; + + /** + * Aborts on a {@code LF ('\n')}. + */ + ByteBufProcessor FIND_LF = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value != '\n'; + } + }; + + /** + * Aborts on a non-{@code LF ('\n')}. + */ + ByteBufProcessor FIND_NON_LF = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value == '\n'; + } + }; + + /** + * Aborts on a {@code CR ('\r')} or a {@code LF ('\n')}. + */ + ByteBufProcessor FIND_CRLF = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value != '\r' && value != '\n'; + } + }; + + /** + * Aborts on a byte which is neither a {@code CR ('\r')} nor a {@code LF ('\n')}. + */ + ByteBufProcessor FIND_NON_CRLF = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value == '\r' || value == '\n'; + } + }; + + /** + * Aborts on a linear whitespace (a ({@code ' '} or a {@code '\t'}). + */ + ByteBufProcessor FIND_LINEAR_WHITESPACE = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value != ' ' && value != '\t'; + } + }; + + /** + * Aborts on a byte which is not a linear whitespace (neither {@code ' '} nor {@code '\t'}). + */ + ByteBufProcessor FIND_NON_LINEAR_WHITESPACE = new ByteBufProcessor() { + @Override + public boolean process(byte value) throws Exception { + return value == ' ' || value == '\t'; + } + }; + + /** + * @return {@code true} if the processor wants to continue the loop and handle the next byte in the buffer. + * {@code false} if the processor wants to stop handling bytes and abort the loop. + */ + boolean process(byte value) throws Exception; +} diff --git a/common/src/common/net/buffer/ByteBufUtil.java b/common/src/common/net/buffer/ByteBufUtil.java new file mode 100644 index 0000000..21b042f --- /dev/null +++ b/common/src/common/net/buffer/ByteBufUtil.java @@ -0,0 +1,483 @@ +/* + * 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 common.net.buffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.util.Locale; + +import common.net.util.CharsetUtil; +import common.net.util.Recycler; +import common.net.util.Recycler.Handle; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.SystemPropertyUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A collection of utility methods that is related with handling {@link ByteBuf}. + */ +public final class ByteBufUtil { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ByteBufUtil.class); + + private static final char[] HEXDUMP_TABLE = new char[256 * 4]; + + static final ByteBufAllocator DEFAULT_ALLOCATOR; + + private static final int THREAD_LOCAL_BUFFER_SIZE; + + static { + final char[] DIGITS = "0123456789abcdef".toCharArray(); + for (int i = 0; i < 256; i ++) { + HEXDUMP_TABLE[ i << 1 ] = DIGITS[i >>> 4 & 0x0F]; + HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F]; + } + + String allocType = SystemPropertyUtil.get("game.net.allocator.type", "unpooled").toLowerCase(Locale.US).trim(); + ByteBufAllocator alloc; + if ("unpooled".equals(allocType)) { + alloc = UnpooledByteBufAllocator.DEFAULT; + logger.debug("-Dgame.net.allocator.type: {}", allocType); + } else if ("pooled".equals(allocType)) { + alloc = PooledByteBufAllocator.DEFAULT; + logger.debug("-Dgame.net.allocator.type: {}", allocType); + } else { + alloc = UnpooledByteBufAllocator.DEFAULT; + logger.debug("-Dgame.net.allocator.type: unpooled (unknown: {})", allocType); + } + + DEFAULT_ALLOCATOR = alloc; + + THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("game.net.threadLocalDirectBufferSize", 64 * 1024); + logger.debug("-Dgame.net.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE); + } + + /** + * Returns a hex dump + * of the specified buffer's readable bytes. + */ + public static String hexDump(ByteBuf buffer) { + return hexDump(buffer, buffer.readerIndex(), buffer.readableBytes()); + } + + /** + * Returns a hex dump + * of the specified buffer's sub-region. + */ + public static String hexDump(ByteBuf buffer, int fromIndex, int length) { + if (length < 0) { + throw new IllegalArgumentException("length: " + length); + } + if (length == 0) { + return ""; + } + + int endIndex = fromIndex + length; + char[] buf = new char[length << 1]; + + int srcIdx = fromIndex; + int dstIdx = 0; + for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) { + System.arraycopy( + HEXDUMP_TABLE, buffer.getUnsignedByte(srcIdx) << 1, + buf, dstIdx, 2); + } + + return new String(buf); + } + + /** + * Calculates the hash code of the specified buffer. This method is + * useful when implementing a new buffer type. + */ + public static int hashCode(ByteBuf buffer) { + final int aLen = buffer.readableBytes(); + final int intCount = aLen >>> 2; + final int byteCount = aLen & 3; + + int hashCode = 1; + int arrayIndex = buffer.readerIndex(); + if (buffer.order() == ByteOrder.BIG_ENDIAN) { + for (int i = intCount; i > 0; i --) { + hashCode = 31 * hashCode + buffer.getInt(arrayIndex); + arrayIndex += 4; + } + } else { + for (int i = intCount; i > 0; i --) { + hashCode = 31 * hashCode + swapInt(buffer.getInt(arrayIndex)); + arrayIndex += 4; + } + } + + for (int i = byteCount; i > 0; i --) { + hashCode = 31 * hashCode + buffer.getByte(arrayIndex ++); + } + + if (hashCode == 0) { + hashCode = 1; + } + + return hashCode; + } + + /** + * Returns {@code true} if and only if the two specified buffers are + * identical to each other as described in {@code ChannelBuffer#equals(Object)}. + * This method is useful when implementing a new buffer type. + */ + public static boolean equals(ByteBuf bufferA, ByteBuf bufferB) { + final int aLen = bufferA.readableBytes(); + if (aLen != bufferB.readableBytes()) { + return false; + } + + final int longCount = aLen >>> 3; + final int byteCount = aLen & 7; + + int aIndex = bufferA.readerIndex(); + int bIndex = bufferB.readerIndex(); + + if (bufferA.order() == bufferB.order()) { + for (int i = longCount; i > 0; i --) { + if (bufferA.getLong(aIndex) != bufferB.getLong(bIndex)) { + return false; + } + aIndex += 8; + bIndex += 8; + } + } else { + for (int i = longCount; i > 0; i --) { + if (bufferA.getLong(aIndex) != swapLong(bufferB.getLong(bIndex))) { + return false; + } + aIndex += 8; + bIndex += 8; + } + } + + for (int i = byteCount; i > 0; i --) { + if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) { + return false; + } + aIndex ++; + bIndex ++; + } + + return true; + } + + /** + * Compares the two specified buffers as described in {@link ByteBuf#compareTo(ByteBuf)}. + * This method is useful when implementing a new buffer type. + */ + public static int compare(ByteBuf bufferA, ByteBuf bufferB) { + final int aLen = bufferA.readableBytes(); + final int bLen = bufferB.readableBytes(); + final int minLength = Math.min(aLen, bLen); + final int uintCount = minLength >>> 2; + final int byteCount = minLength & 3; + + int aIndex = bufferA.readerIndex(); + int bIndex = bufferB.readerIndex(); + + if (bufferA.order() == bufferB.order()) { + for (int i = uintCount; i > 0; i --) { + long va = bufferA.getUnsignedInt(aIndex); + long vb = bufferB.getUnsignedInt(bIndex); + if (va > vb) { + return 1; + } + if (va < vb) { + return -1; + } + aIndex += 4; + bIndex += 4; + } + } else { + for (int i = uintCount; i > 0; i --) { + long va = bufferA.getUnsignedInt(aIndex); + long vb = swapInt(bufferB.getInt(bIndex)) & 0xFFFFFFFFL; + if (va > vb) { + return 1; + } + if (va < vb) { + return -1; + } + aIndex += 4; + bIndex += 4; + } + } + + for (int i = byteCount; i > 0; i --) { + short va = bufferA.getUnsignedByte(aIndex); + short vb = bufferB.getUnsignedByte(bIndex); + if (va > vb) { + return 1; + } + if (va < vb) { + return -1; + } + aIndex ++; + bIndex ++; + } + + return aLen - bLen; + } + + /** + * The default implementation of {@link ByteBuf#indexOf(int, int, byte)}. + * This method is useful when implementing a new buffer type. + */ + public static int indexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) { + if (fromIndex <= toIndex) { + return firstIndexOf(buffer, fromIndex, toIndex, value); + } else { + return lastIndexOf(buffer, fromIndex, toIndex, value); + } + } + + /** + * Toggles the endianness of the specified 16-bit short integer. + */ + public static short swapShort(short value) { + return Short.reverseBytes(value); + } + + /** + * Toggles the endianness of the specified 24-bit medium integer. + */ + public static int swapMedium(int value) { + int swapped = value << 16 & 0xff0000 | value & 0xff00 | value >>> 16 & 0xff; + if ((swapped & 0x800000) != 0) { + swapped |= 0xff000000; + } + return swapped; + } + + /** + * Toggles the endianness of the specified 32-bit integer. + */ + public static int swapInt(int value) { + return Integer.reverseBytes(value); + } + + /** + * Toggles the endianness of the specified 64-bit long integer. + */ + public static long swapLong(long value) { + return Long.reverseBytes(value); + } + + /** + * Read the given amount of bytes into a new {@link ByteBuf} that is allocated from the {@link ByteBufAllocator}. + */ + public static ByteBuf readBytes(ByteBufAllocator alloc, ByteBuf buffer, int length) { + boolean release = true; + ByteBuf dst = alloc.buffer(length); + try { + buffer.readBytes(dst); + release = false; + return dst; + } finally { + if (release) { + dst.release(); + } + } + } + + private static int firstIndexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) { + fromIndex = Math.max(fromIndex, 0); + if (fromIndex >= toIndex || buffer.capacity() == 0) { + return -1; + } + + for (int i = fromIndex; i < toIndex; i ++) { + if (buffer.getByte(i) == value) { + return i; + } + } + + return -1; + } + + private static int lastIndexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) { + fromIndex = Math.min(fromIndex, buffer.capacity()); + if (fromIndex < 0 || buffer.capacity() == 0) { + return -1; + } + + for (int i = fromIndex - 1; i >= toIndex; i --) { + if (buffer.getByte(i) == value) { + return i; + } + } + + return -1; + } + + /** + * Encode the given {@link CharBuffer} using the given {@link Charset} into a new {@link ByteBuf} which + * is allocated via the {@link ByteBufAllocator}. + */ + public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset) { + return encodeString0(alloc, false, src, charset); + } + + static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset) { + final CharsetEncoder encoder = CharsetUtil.getEncoder(charset); + int length = (int) ((double) src.remaining() * encoder.maxBytesPerChar()); + boolean release = true; + final ByteBuf dst; + if (enforceHeap) { + dst = alloc.heapBuffer(length); + } else { + dst = alloc.buffer(length); + } + try { + final ByteBuffer dstBuf = dst.internalNioBuffer(0, length); + final int pos = dstBuf.position(); + CoderResult cr = encoder.encode(src, dstBuf, true); + if (!cr.isUnderflow()) { + cr.throwException(); + } + cr = encoder.flush(dstBuf); + if (!cr.isUnderflow()) { + cr.throwException(); + } + dst.writerIndex(dst.writerIndex() + dstBuf.position() - pos); + release = false; + return dst; + } catch (CharacterCodingException x) { + throw new IllegalStateException(x); + } finally { + if (release) { + dst.release(); + } + } + } + + static String decodeString(ByteBuffer src, Charset charset) { + final CharsetDecoder decoder = CharsetUtil.getDecoder(charset); + final CharBuffer dst = CharBuffer.allocate( + (int) ((double) src.remaining() * decoder.maxCharsPerByte())); + try { + CoderResult cr = decoder.decode(src, dst, true); + if (!cr.isUnderflow()) { + cr.throwException(); + } + cr = decoder.flush(dst); + if (!cr.isUnderflow()) { + cr.throwException(); + } + } catch (CharacterCodingException x) { + throw new IllegalStateException(x); + } + return dst.flip().toString(); + } + + /** + * Returns a cached thread-local direct buffer, if available. + * + * @return a cached thread-local direct buffer, if available. {@code null} otherwise. + */ + public static ByteBuf threadLocalDirectBuffer() { + if (THREAD_LOCAL_BUFFER_SIZE <= 0) { + return null; + } + + if (PlatformDependent.hasUnsafe()) { + return ThreadLocalUnsafeDirectByteBuf.newInstance(); + } else { + return ThreadLocalDirectByteBuf.newInstance(); + } + } + + static final class ThreadLocalUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf { + + private static final Recycler RECYCLER = + new Recycler() { + @Override + protected ThreadLocalUnsafeDirectByteBuf newObject(Handle handle) { + return new ThreadLocalUnsafeDirectByteBuf(handle); + } + }; + + static ThreadLocalUnsafeDirectByteBuf newInstance() { + ThreadLocalUnsafeDirectByteBuf buf = RECYCLER.get(); + buf.setRefCnt(1); + return buf; + } + + private final Handle handle; + + private ThreadLocalUnsafeDirectByteBuf(Handle handle) { + super(UnpooledByteBufAllocator.DEFAULT, 256, Integer.MAX_VALUE); + this.handle = handle; + } + + @Override + protected void deallocate() { + if (capacity() > THREAD_LOCAL_BUFFER_SIZE) { + super.deallocate(); + } else { + clear(); + RECYCLER.recycle(this, handle); + } + } + } + + static final class ThreadLocalDirectByteBuf extends UnpooledDirectByteBuf { + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected ThreadLocalDirectByteBuf newObject(Handle handle) { + return new ThreadLocalDirectByteBuf(handle); + } + }; + + static ThreadLocalDirectByteBuf newInstance() { + ThreadLocalDirectByteBuf buf = RECYCLER.get(); + buf.setRefCnt(1); + return buf; + } + + private final Handle handle; + + private ThreadLocalDirectByteBuf(Handle handle) { + super(UnpooledByteBufAllocator.DEFAULT, 256, Integer.MAX_VALUE); + this.handle = handle; + } + + @Override + protected void deallocate() { + if (capacity() > THREAD_LOCAL_BUFFER_SIZE) { + super.deallocate(); + } else { + clear(); + RECYCLER.recycle(this, handle); + } + } + } + + private ByteBufUtil() { } +} diff --git a/common/src/common/net/buffer/CompositeByteBuf.java b/common/src/common/net/buffer/CompositeByteBuf.java new file mode 100644 index 0000000..1848209 --- /dev/null +++ b/common/src/common/net/buffer/CompositeByteBuf.java @@ -0,0 +1,1602 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import common.net.util.ResourceLeak; +import common.net.util.internal.EmptyArrays; + +/** + * A virtual buffer which shows multiple buffers as a single merged buffer. It is recommended to use + * {@link ByteBufAllocator#compositeBuffer()} or {@link Unpooled#wrappedBuffer(ByteBuf...)} instead of calling the + * constructor explicitly. + */ +public class CompositeByteBuf extends AbstractReferenceCountedByteBuf { + + private final ResourceLeak leak; + private final ByteBufAllocator alloc; + private final boolean direct; + private final List components = new ArrayList(); + private final int maxNumComponents; + private static final ByteBuffer FULL_BYTEBUFFER = (ByteBuffer) ByteBuffer.allocate(1).position(1); + + private boolean freed; + + public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) { + super(Integer.MAX_VALUE); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + this.alloc = alloc; + this.direct = direct; + this.maxNumComponents = maxNumComponents; + leak = leakDetector.open(this); + } + + public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) { + super(Integer.MAX_VALUE); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (maxNumComponents < 2) { + throw new IllegalArgumentException( + "maxNumComponents: " + maxNumComponents + " (expected: >= 2)"); + } + + this.alloc = alloc; + this.direct = direct; + this.maxNumComponents = maxNumComponents; + + addComponents0(0, buffers); + consolidateIfNeeded(); + setIndex(0, capacity()); + leak = leakDetector.open(this); + } + + public CompositeByteBuf( + ByteBufAllocator alloc, boolean direct, int maxNumComponents, Iterable buffers) { + super(Integer.MAX_VALUE); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (maxNumComponents < 2) { + throw new IllegalArgumentException( + "maxNumComponents: " + maxNumComponents + " (expected: >= 2)"); + } + + this.alloc = alloc; + this.direct = direct; + this.maxNumComponents = maxNumComponents; + addComponents0(0, buffers); + consolidateIfNeeded(); + setIndex(0, capacity()); + leak = leakDetector.open(this); + } + + /** + * Add the given {@link ByteBuf}. + * + * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. + * If you need to have it increased you need to handle it by your own. + * + * @param buffer the {@link ByteBuf} to add + */ + public CompositeByteBuf addComponent(ByteBuf buffer) { + addComponent0(components.size(), buffer); + consolidateIfNeeded(); + return this; + } + + /** + * Add the given {@link ByteBuf}s. + * + * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. + * If you need to have it increased you need to handle it by your own. + * + * @param buffers the {@link ByteBuf}s to add + */ + public CompositeByteBuf addComponents(ByteBuf... buffers) { + addComponents0(components.size(), buffers); + consolidateIfNeeded(); + return this; + } + + /** + * Add the given {@link ByteBuf}s. + * + * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. + * If you need to have it increased you need to handle it by your own. + * + * @param buffers the {@link ByteBuf}s to add + */ + public CompositeByteBuf addComponents(Iterable buffers) { + addComponents0(components.size(), buffers); + consolidateIfNeeded(); + return this; + } + + /** + * Add the given {@link ByteBuf} on the specific index. + * + * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. + * If you need to have it increased you need to handle it by your own. + * + * @param cIndex the index on which the {@link ByteBuf} will be added + * @param buffer the {@link ByteBuf} to add + */ + public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { + addComponent0(cIndex, buffer); + consolidateIfNeeded(); + return this; + } + + private int addComponent0(int cIndex, ByteBuf buffer) { + checkComponentIndex(cIndex); + + if (buffer == null) { + throw new NullPointerException("buffer"); + } + + int readableBytes = buffer.readableBytes(); + if (readableBytes == 0) { + return cIndex; + } + + // No need to consolidate - just add a component to the list. + Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice()); + if (cIndex == components.size()) { + components.add(c); + if (cIndex == 0) { + c.endOffset = readableBytes; + } else { + Component prev = components.get(cIndex - 1); + c.offset = prev.endOffset; + c.endOffset = c.offset + readableBytes; + } + } else { + components.add(cIndex, c); + updateComponentOffsets(cIndex); + } + return cIndex; + } + + /** + * Add the given {@link ByteBuf}s on the specific index + * + * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. + * If you need to have it increased you need to handle it by your own. + * + * @param cIndex the index on which the {@link ByteBuf} will be added. + * @param buffers the {@link ByteBuf}s to add + */ + public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) { + addComponents0(cIndex, buffers); + consolidateIfNeeded(); + return this; + } + + private int addComponents0(int cIndex, ByteBuf... buffers) { + checkComponentIndex(cIndex); + + if (buffers == null) { + throw new NullPointerException("buffers"); + } + + int readableBytes = 0; + for (ByteBuf b: buffers) { + if (b == null) { + break; + } + readableBytes += b.readableBytes(); + } + + if (readableBytes == 0) { + return cIndex; + } + + // No need for consolidation + for (ByteBuf b: buffers) { + if (b == null) { + break; + } + if (b.isReadable()) { + cIndex = addComponent0(cIndex, b) + 1; + int size = components.size(); + if (cIndex > size) { + cIndex = size; + } + } else { + b.release(); + } + } + return cIndex; + } + + /** + * Add the given {@link ByteBuf}s on the specific index + * + * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. + * If you need to have it increased you need to handle it by your own. + * + * @param cIndex the index on which the {@link ByteBuf} will be added. + * @param buffers the {@link ByteBuf}s to add + */ + public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { + addComponents0(cIndex, buffers); + consolidateIfNeeded(); + return this; + } + + private int addComponents0(int cIndex, Iterable buffers) { + if (buffers == null) { + throw new NullPointerException("buffers"); + } + + if (buffers instanceof ByteBuf) { + // If buffers also implements ByteBuf (e.g. CompositeByteBuf), it has to go to addComponent(ByteBuf). + return addComponent0(cIndex, (ByteBuf) buffers); + } + + if (!(buffers instanceof Collection)) { + List list = new ArrayList(); + for (ByteBuf b: buffers) { + list.add(b); + } + buffers = list; + } + + Collection col = (Collection) buffers; + return addComponents0(cIndex, col.toArray(new ByteBuf[col.size()])); + } + + /** + * This should only be called as last operation from a method as this may adjust the underlying + * array of components and so affect the index etc. + */ + private void consolidateIfNeeded() { + // Consolidate if the number of components will exceed the allowed maximum by the current + // operation. + final int numComponents = components.size(); + if (numComponents > maxNumComponents) { + final int capacity = components.get(numComponents - 1).endOffset; + + ByteBuf consolidated = allocBuffer(capacity); + + // We're not using foreach to avoid creating an iterator. + for (int i = 0; i < numComponents; i ++) { + Component c = components.get(i); + ByteBuf b = c.buf; + consolidated.writeBytes(b); + c.freeIfNecessary(); + } + Component c = new Component(consolidated); + c.endOffset = c.length; + components.clear(); + components.add(c); + } + } + + private void checkComponentIndex(int cIndex) { + ensureAccessible(); + if (cIndex < 0 || cIndex > components.size()) { + throw new IndexOutOfBoundsException(String.format( + "cIndex: %d (expected: >= 0 && <= numComponents(%d))", + cIndex, components.size())); + } + } + + private void checkComponentIndex(int cIndex, int numComponents) { + ensureAccessible(); + if (cIndex < 0 || cIndex + numComponents > components.size()) { + throw new IndexOutOfBoundsException(String.format( + "cIndex: %d, numComponents: %d " + + "(expected: cIndex >= 0 && cIndex + numComponents <= totalNumComponents(%d))", + cIndex, numComponents, components.size())); + } + } + + private void updateComponentOffsets(int cIndex) { + int size = components.size(); + if (size <= cIndex) { + return; + } + + Component c = components.get(cIndex); + if (cIndex == 0) { + c.offset = 0; + c.endOffset = c.length; + cIndex ++; + } + + for (int i = cIndex; i < size; i ++) { + Component prev = components.get(i - 1); + Component cur = components.get(i); + cur.offset = prev.endOffset; + cur.endOffset = cur.offset + cur.length; + } + } + + /** + * Remove the {@link ByteBuf} from the given index. + * + * @param cIndex the index on from which the {@link ByteBuf} will be remove + */ + public CompositeByteBuf removeComponent(int cIndex) { + checkComponentIndex(cIndex); + components.remove(cIndex).freeIfNecessary(); + updateComponentOffsets(cIndex); + return this; + } + + /** + * Remove the number of {@link ByteBuf}s starting from the given index. + * + * @param cIndex the index on which the {@link ByteBuf}s will be started to removed + * @param numComponents the number of components to remove + */ + public CompositeByteBuf removeComponents(int cIndex, int numComponents) { + checkComponentIndex(cIndex, numComponents); + + List toRemove = components.subList(cIndex, cIndex + numComponents); + for (Component c: toRemove) { + c.freeIfNecessary(); + } + toRemove.clear(); + + updateComponentOffsets(cIndex); + return this; + } + + public Iterator iterator() { + ensureAccessible(); + List list = new ArrayList(components.size()); + for (Component c: components) { + list.add(c.buf); + } + return list.iterator(); + } + + /** + * Same with {@link #slice(int, int)} except that this method returns a list. + */ + public List decompose(int offset, int length) { + checkIndex(offset, length); + if (length == 0) { + return Collections.emptyList(); + } + + int componentId = toComponentIndex(offset); + List slice = new ArrayList(components.size()); + + // The first component + Component firstC = components.get(componentId); + ByteBuf first = firstC.buf.duplicate(); + first.readerIndex(offset - firstC.offset); + + ByteBuf buf = first; + int bytesToSlice = length; + do { + int readableBytes = buf.readableBytes(); + if (bytesToSlice <= readableBytes) { + // Last component + buf.writerIndex(buf.readerIndex() + bytesToSlice); + slice.add(buf); + break; + } else { + // Not the last component + slice.add(buf); + bytesToSlice -= readableBytes; + componentId ++; + + // Fetch the next component. + buf = components.get(componentId).buf.duplicate(); + } + } while (bytesToSlice > 0); + + // Slice all components because only readable bytes are interesting. + for (int i = 0; i < slice.size(); i ++) { + slice.set(i, slice.get(i).slice()); + } + + return slice; + } + + @Override + public boolean isDirect() { + int size = components.size(); + if (size == 0) { + return false; + } + for (int i = 0; i < size; i++) { + if (!components.get(i).buf.isDirect()) { + return false; + } + } + return true; + } + + @Override + public boolean hasArray() { + if (components.size() == 1) { + return components.get(0).buf.hasArray(); + } + return false; + } + + @Override + public byte[] array() { + if (components.size() == 1) { + return components.get(0).buf.array(); + } + throw new UnsupportedOperationException(); + } + + @Override + public int arrayOffset() { + if (components.size() == 1) { + return components.get(0).buf.arrayOffset(); + } + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasMemoryAddress() { + if (components.size() == 1) { + return components.get(0).buf.hasMemoryAddress(); + } + return false; + } + + @Override + public long memoryAddress() { + if (components.size() == 1) { + return components.get(0).buf.memoryAddress(); + } + throw new UnsupportedOperationException(); + } + + @Override + public int capacity() { + if (components.isEmpty()) { + return 0; + } + return components.get(components.size() - 1).endOffset; + } + + @Override + public CompositeByteBuf capacity(int newCapacity) { + ensureAccessible(); + if (newCapacity < 0 || newCapacity > maxCapacity()) { + throw new IllegalArgumentException("newCapacity: " + newCapacity); + } + + int oldCapacity = capacity(); + if (newCapacity > oldCapacity) { + final int paddingLength = newCapacity - oldCapacity; + ByteBuf padding; + int nComponents = components.size(); + if (nComponents < maxNumComponents) { + padding = allocBuffer(paddingLength); + padding.setIndex(0, paddingLength); + addComponent0(components.size(), padding); + } else { + padding = allocBuffer(paddingLength); + padding.setIndex(0, paddingLength); + // FIXME: No need to create a padding buffer and consolidate. + // Just create a big single buffer and put the current content there. + addComponent0(components.size(), padding); + consolidateIfNeeded(); + } + } else if (newCapacity < oldCapacity) { + int bytesToTrim = oldCapacity - newCapacity; + for (ListIterator i = components.listIterator(components.size()); i.hasPrevious();) { + Component c = i.previous(); + if (bytesToTrim >= c.length) { + bytesToTrim -= c.length; + i.remove(); + continue; + } + + // Replace the last component with the trimmed slice. + Component newC = new Component(c.buf.slice(0, c.length - bytesToTrim)); + newC.offset = c.offset; + newC.endOffset = newC.offset + newC.length; + i.set(newC); + break; + } + + if (readerIndex() > newCapacity) { + setIndex(newCapacity, newCapacity); + } else if (writerIndex() > newCapacity) { + writerIndex(newCapacity); + } + } + return this; + } + + @Override + public ByteBufAllocator alloc() { + return alloc; + } + + @Override + public ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + /** + * Return the current number of {@link ByteBuf}'s that are composed in this instance + */ + public int numComponents() { + return components.size(); + } + + /** + * Return the max number of {@link ByteBuf}'s that are composed in this instance + */ + public int maxNumComponents() { + return maxNumComponents; + } + + /** + * Return the index for the given offset + */ + public int toComponentIndex(int offset) { + checkIndex(offset); + + for (int low = 0, high = components.size(); low <= high;) { + int mid = low + high >>> 1; + Component c = components.get(mid); + if (offset >= c.endOffset) { + low = mid + 1; + } else if (offset < c.offset) { + high = mid - 1; + } else { + return mid; + } + } + + throw new Error("should not reach here"); + } + + public int toByteIndex(int cIndex) { + checkComponentIndex(cIndex); + return components.get(cIndex).offset; + } + + @Override + public byte getByte(int index) { + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + Component c = findComponent(index); + return c.buf.getByte(index - c.offset); + } + + @Override + protected short _getShort(int index) { + Component c = findComponent(index); + if (index + 2 <= c.endOffset) { + return c.buf.getShort(index - c.offset); + } else if (order() == ByteOrder.BIG_ENDIAN) { + return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); + } else { + return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); + } + } + + @Override + protected int _getUnsignedMedium(int index) { + Component c = findComponent(index); + if (index + 3 <= c.endOffset) { + return c.buf.getUnsignedMedium(index - c.offset); + } else if (order() == ByteOrder.BIG_ENDIAN) { + return (_getShort(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; + } else { + return _getShort(index) & 0xFFFF | (_getByte(index + 2) & 0xFF) << 16; + } + } + + @Override + protected int _getInt(int index) { + Component c = findComponent(index); + if (index + 4 <= c.endOffset) { + return c.buf.getInt(index - c.offset); + } else if (order() == ByteOrder.BIG_ENDIAN) { + return (_getShort(index) & 0xffff) << 16 | _getShort(index + 2) & 0xffff; + } else { + return _getShort(index) & 0xFFFF | (_getShort(index + 2) & 0xFFFF) << 16; + } + } + + @Override + protected long _getLong(int index) { + Component c = findComponent(index); + if (index + 8 <= c.endOffset) { + return c.buf.getLong(index - c.offset); + } else if (order() == ByteOrder.BIG_ENDIAN) { + return (_getInt(index) & 0xffffffffL) << 32 | _getInt(index + 4) & 0xffffffffL; + } else { + return _getInt(index) & 0xFFFFFFFFL | (_getInt(index + 4) & 0xFFFFFFFFL) << 32; + } + } + + @Override + public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.length); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + s.getBytes(index - adjustment, dst, dstIndex, localLength); + index += localLength; + dstIndex += localLength; + length -= localLength; + i ++; + } + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuffer dst) { + int limit = dst.limit(); + int length = dst.remaining(); + + checkIndex(index, length); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + try { + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + dst.limit(dst.position() + localLength); + s.getBytes(index - adjustment, dst); + index += localLength; + length -= localLength; + i ++; + } + } finally { + dst.limit(limit); + } + return this; + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + s.getBytes(index - adjustment, dst, dstIndex, localLength); + index += localLength; + dstIndex += localLength; + length -= localLength; + i ++; + } + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) + throws IOException { + int count = nioBufferCount(); + if (count == 1) { + return out.write(internalNioBuffer(index, length)); + } else { + long writtenBytes = out.write(nioBuffers(index, length)); + if (writtenBytes > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) writtenBytes; + } + } + } + + @Override + public CompositeByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + checkIndex(index, length); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + s.getBytes(index - adjustment, out, localLength); + index += localLength; + length -= localLength; + i ++; + } + return this; + } + + @Override + public CompositeByteBuf setByte(int index, int value) { + Component c = findComponent(index); + c.buf.setByte(index - c.offset, value); + return this; + } + + @Override + protected void _setByte(int index, int value) { + setByte(index, value); + } + + @Override + public CompositeByteBuf setShort(int index, int value) { + return (CompositeByteBuf) super.setShort(index, value); + } + + @Override + protected void _setShort(int index, int value) { + Component c = findComponent(index); + if (index + 2 <= c.endOffset) { + c.buf.setShort(index - c.offset, value); + } else if (order() == ByteOrder.BIG_ENDIAN) { + _setByte(index, (byte) (value >>> 8)); + _setByte(index + 1, (byte) value); + } else { + _setByte(index, (byte) value); + _setByte(index + 1, (byte) (value >>> 8)); + } + } + + @Override + public CompositeByteBuf setMedium(int index, int value) { + return (CompositeByteBuf) super.setMedium(index, value); + } + + @Override + protected void _setMedium(int index, int value) { + Component c = findComponent(index); + if (index + 3 <= c.endOffset) { + c.buf.setMedium(index - c.offset, value); + } else if (order() == ByteOrder.BIG_ENDIAN) { + _setShort(index, (short) (value >> 8)); + _setByte(index + 2, (byte) value); + } else { + _setShort(index, (short) value); + _setByte(index + 2, (byte) (value >>> 16)); + } + } + + @Override + public CompositeByteBuf setInt(int index, int value) { + return (CompositeByteBuf) super.setInt(index, value); + } + + @Override + protected void _setInt(int index, int value) { + Component c = findComponent(index); + if (index + 4 <= c.endOffset) { + c.buf.setInt(index - c.offset, value); + } else if (order() == ByteOrder.BIG_ENDIAN) { + _setShort(index, (short) (value >>> 16)); + _setShort(index + 2, (short) value); + } else { + _setShort(index, (short) value); + _setShort(index + 2, (short) (value >>> 16)); + } + } + + @Override + public CompositeByteBuf setLong(int index, long value) { + return (CompositeByteBuf) super.setLong(index, value); + } + + @Override + protected void _setLong(int index, long value) { + Component c = findComponent(index); + if (index + 8 <= c.endOffset) { + c.buf.setLong(index - c.offset, value); + } else if (order() == ByteOrder.BIG_ENDIAN) { + _setInt(index, (int) (value >>> 32)); + _setInt(index + 4, (int) value); + } else { + _setInt(index, (int) value); + _setInt(index + 4, (int) (value >>> 32)); + } + } + + @Override + public CompositeByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.length); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + s.setBytes(index - adjustment, src, srcIndex, localLength); + index += localLength; + srcIndex += localLength; + length -= localLength; + i ++; + } + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuffer src) { + int limit = src.limit(); + int length = src.remaining(); + + checkIndex(index, length); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + try { + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + src.limit(src.position() + localLength); + s.setBytes(index - adjustment, src); + index += localLength; + length -= localLength; + i ++; + } + } finally { + src.limit(limit); + } + return this; + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.capacity()); + if (length == 0) { + return this; + } + + int i = toComponentIndex(index); + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + s.setBytes(index - adjustment, src, srcIndex, localLength); + index += localLength; + srcIndex += localLength; + length -= localLength; + i ++; + } + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + checkIndex(index, length); + if (length == 0) { + return in.read(EmptyArrays.EMPTY_BYTES); + } + + int i = toComponentIndex(index); + int readBytes = 0; + + do { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + int localReadBytes = s.setBytes(index - adjustment, in, localLength); + if (localReadBytes < 0) { + if (readBytes == 0) { + return -1; + } else { + break; + } + } + + if (localReadBytes == localLength) { + index += localLength; + length -= localLength; + readBytes += localLength; + i ++; + } else { + index += localReadBytes; + length -= localReadBytes; + readBytes += localReadBytes; + } + } while (length > 0); + + return readBytes; + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + checkIndex(index, length); + if (length == 0) { + return in.read(FULL_BYTEBUFFER); + } + + int i = toComponentIndex(index); + int readBytes = 0; + do { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + int localReadBytes = s.setBytes(index - adjustment, in, localLength); + + if (localReadBytes == 0) { + break; + } + + if (localReadBytes < 0) { + if (readBytes == 0) { + return -1; + } else { + break; + } + } + + if (localReadBytes == localLength) { + index += localLength; + length -= localLength; + readBytes += localLength; + i ++; + } else { + index += localReadBytes; + length -= localReadBytes; + readBytes += localReadBytes; + } + } while (length > 0); + + return readBytes; + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + ByteBuf dst = Unpooled.buffer(length); + if (length != 0) { + copyTo(index, length, toComponentIndex(index), dst); + } + return dst; + } + + private void copyTo(int index, int length, int componentId, ByteBuf dst) { + int dstIndex = 0; + int i = componentId; + + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + s.getBytes(index - adjustment, dst, dstIndex, localLength); + index += localLength; + dstIndex += localLength; + length -= localLength; + i ++; + } + + dst.writerIndex(dst.capacity()); + } + + /** + * Return the {@link ByteBuf} on the specified index + * + * @param cIndex the index for which the {@link ByteBuf} should be returned + * @return buf the {@link ByteBuf} on the specified index + */ + public ByteBuf component(int cIndex) { + return internalComponent(cIndex).duplicate(); + } + + /** + * Return the {@link ByteBuf} on the specified index + * + * @param offset the offset for which the {@link ByteBuf} should be returned + * @return the {@link ByteBuf} on the specified index + */ + public ByteBuf componentAtOffset(int offset) { + return internalComponentAtOffset(offset).duplicate(); + } + + /** + * Return the internal {@link ByteBuf} on the specified index. Note that updating the indexes of the returned + * buffer will lead to an undefined behavior of this buffer. + * + * @param cIndex the index for which the {@link ByteBuf} should be returned + */ + public ByteBuf internalComponent(int cIndex) { + checkComponentIndex(cIndex); + return components.get(cIndex).buf; + } + + /** + * Return the internal {@link ByteBuf} on the specified offset. Note that updating the indexes of the returned + * buffer will lead to an undefined behavior of this buffer. + * + * @param offset the offset for which the {@link ByteBuf} should be returned + */ + public ByteBuf internalComponentAtOffset(int offset) { + return findComponent(offset).buf; + } + + private Component findComponent(int offset) { + checkIndex(offset); + + for (int low = 0, high = components.size(); low <= high;) { + int mid = low + high >>> 1; + Component c = components.get(mid); + if (offset >= c.endOffset) { + low = mid + 1; + } else if (offset < c.offset) { + high = mid - 1; + } else { + return c; + } + } + + throw new Error("should not reach here"); + } + + @Override + public int nioBufferCount() { + if (components.size() == 1) { + return components.get(0).buf.nioBufferCount(); + } else { + int count = 0; + int componentsCount = components.size(); + for (int i = 0; i < componentsCount; i++) { + Component c = components.get(i); + count += c.buf.nioBufferCount(); + } + return count; + } + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + if (components.size() == 1) { + return components.get(0).buf.internalNioBuffer(index, length); + } + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + if (components.size() == 1) { + ByteBuf buf = components.get(0).buf; + if (buf.nioBufferCount() == 1) { + return components.get(0).buf.nioBuffer(index, length); + } + } + ByteBuffer merged = ByteBuffer.allocate(length).order(order()); + ByteBuffer[] buffers = nioBuffers(index, length); + + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < buffers.length; i++) { + merged.put(buffers[i]); + } + + merged.flip(); + return merged; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + checkIndex(index, length); + if (length == 0) { + return EmptyArrays.EMPTY_BYTE_BUFFERS; + } + + List buffers = new ArrayList(components.size()); + int i = toComponentIndex(index); + while (length > 0) { + Component c = components.get(i); + ByteBuf s = c.buf; + int adjustment = c.offset; + int localLength = Math.min(length, s.capacity() - (index - adjustment)); + switch (s.nioBufferCount()) { + case 0: + throw new UnsupportedOperationException(); + case 1: + buffers.add(s.nioBuffer(index - adjustment, localLength)); + break; + default: + Collections.addAll(buffers, s.nioBuffers(index - adjustment, localLength)); + } + + index += localLength; + length -= localLength; + i ++; + } + + return buffers.toArray(new ByteBuffer[buffers.size()]); + } + + /** + * Consolidate the composed {@link ByteBuf}s + */ + public CompositeByteBuf consolidate() { + ensureAccessible(); + final int numComponents = numComponents(); + if (numComponents <= 1) { + return this; + } + + final Component last = components.get(numComponents - 1); + final int capacity = last.endOffset; + final ByteBuf consolidated = allocBuffer(capacity); + + for (int i = 0; i < numComponents; i ++) { + Component c = components.get(i); + ByteBuf b = c.buf; + consolidated.writeBytes(b); + c.freeIfNecessary(); + } + + components.clear(); + components.add(new Component(consolidated)); + updateComponentOffsets(0); + return this; + } + + /** + * Consolidate the composed {@link ByteBuf}s + * + * @param cIndex the index on which to start to compose + * @param numComponents the number of components to compose + */ + public CompositeByteBuf consolidate(int cIndex, int numComponents) { + checkComponentIndex(cIndex, numComponents); + if (numComponents <= 1) { + return this; + } + + final int endCIndex = cIndex + numComponents; + final Component last = components.get(endCIndex - 1); + final int capacity = last.endOffset - components.get(cIndex).offset; + final ByteBuf consolidated = allocBuffer(capacity); + + for (int i = cIndex; i < endCIndex; i ++) { + Component c = components.get(i); + ByteBuf b = c.buf; + consolidated.writeBytes(b); + c.freeIfNecessary(); + } + + components.subList(cIndex + 1, endCIndex).clear(); + components.set(cIndex, new Component(consolidated)); + updateComponentOffsets(cIndex); + return this; + } + + /** + * Discard all {@link ByteBuf}s which are read. + */ + public CompositeByteBuf discardReadComponents() { + ensureAccessible(); + final int readerIndex = readerIndex(); + if (readerIndex == 0) { + return this; + } + + // Discard everything if (readerIndex = writerIndex = capacity). + int writerIndex = writerIndex(); + if (readerIndex == writerIndex && writerIndex == capacity()) { + for (Component c: components) { + c.freeIfNecessary(); + } + components.clear(); + setIndex(0, 0); + adjustMarkers(readerIndex); + return this; + } + + // Remove read components. + int firstComponentId = toComponentIndex(readerIndex); + for (int i = 0; i < firstComponentId; i ++) { + components.get(i).freeIfNecessary(); + } + components.subList(0, firstComponentId).clear(); + + // Update indexes and markers. + Component first = components.get(0); + int offset = first.offset; + updateComponentOffsets(0); + setIndex(readerIndex - offset, writerIndex - offset); + adjustMarkers(offset); + return this; + } + + @Override + public CompositeByteBuf discardReadBytes() { + ensureAccessible(); + final int readerIndex = readerIndex(); + if (readerIndex == 0) { + return this; + } + + // Discard everything if (readerIndex = writerIndex = capacity). + int writerIndex = writerIndex(); + if (readerIndex == writerIndex && writerIndex == capacity()) { + for (Component c: components) { + c.freeIfNecessary(); + } + components.clear(); + setIndex(0, 0); + adjustMarkers(readerIndex); + return this; + } + + // Remove read components. + int firstComponentId = toComponentIndex(readerIndex); + for (int i = 0; i < firstComponentId; i ++) { + components.get(i).freeIfNecessary(); + } + components.subList(0, firstComponentId).clear(); + + // Remove or replace the first readable component with a new slice. + Component c = components.get(0); + int adjustment = readerIndex - c.offset; + if (adjustment == c.length) { + // new slice would be empty, so remove instead + components.remove(0); + } else { + Component newC = new Component(c.buf.slice(adjustment, c.length - adjustment)); + components.set(0, newC); + } + + // Update indexes and markers. + updateComponentOffsets(0); + setIndex(0, writerIndex - readerIndex); + adjustMarkers(readerIndex); + return this; + } + + private ByteBuf allocBuffer(int capacity) { + if (direct) { + return alloc().directBuffer(capacity); + } + return alloc().heapBuffer(capacity); + } + + @Override + public String toString() { + String result = super.toString(); + result = result.substring(0, result.length() - 1); + return result + ", components=" + components.size() + ')'; + } + + private static final class Component { + final ByteBuf buf; + final int length; + int offset; + int endOffset; + + Component(ByteBuf buf) { + this.buf = buf; + length = buf.readableBytes(); + } + + void freeIfNecessary() { + // Unwrap so that we can free slices, too. + buf.release(); // We should not get a NPE here. If so, it must be a bug. + } + } + + @Override + public CompositeByteBuf readerIndex(int readerIndex) { + return (CompositeByteBuf) super.readerIndex(readerIndex); + } + + @Override + public CompositeByteBuf writerIndex(int writerIndex) { + return (CompositeByteBuf) super.writerIndex(writerIndex); + } + + @Override + public CompositeByteBuf setIndex(int readerIndex, int writerIndex) { + return (CompositeByteBuf) super.setIndex(readerIndex, writerIndex); + } + + @Override + public CompositeByteBuf clear() { + return (CompositeByteBuf) super.clear(); + } + + @Override + public CompositeByteBuf markReaderIndex() { + return (CompositeByteBuf) super.markReaderIndex(); + } + + @Override + public CompositeByteBuf resetReaderIndex() { + return (CompositeByteBuf) super.resetReaderIndex(); + } + + @Override + public CompositeByteBuf markWriterIndex() { + return (CompositeByteBuf) super.markWriterIndex(); + } + + @Override + public CompositeByteBuf resetWriterIndex() { + return (CompositeByteBuf) super.resetWriterIndex(); + } + + @Override + public CompositeByteBuf ensureWritable(int minWritableBytes) { + return (CompositeByteBuf) super.ensureWritable(minWritableBytes); + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst) { + return (CompositeByteBuf) super.getBytes(index, dst); + } + + @Override + public CompositeByteBuf getBytes(int index, ByteBuf dst, int length) { + return (CompositeByteBuf) super.getBytes(index, dst, length); + } + + @Override + public CompositeByteBuf getBytes(int index, byte[] dst) { + return (CompositeByteBuf) super.getBytes(index, dst); + } + + @Override + public CompositeByteBuf setBoolean(int index, boolean value) { + return (CompositeByteBuf) super.setBoolean(index, value); + } + + @Override + public CompositeByteBuf setChar(int index, int value) { + return (CompositeByteBuf) super.setChar(index, value); + } + + @Override + public CompositeByteBuf setFloat(int index, float value) { + return (CompositeByteBuf) super.setFloat(index, value); + } + + @Override + public CompositeByteBuf setDouble(int index, double value) { + return (CompositeByteBuf) super.setDouble(index, value); + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src) { + return (CompositeByteBuf) super.setBytes(index, src); + } + + @Override + public CompositeByteBuf setBytes(int index, ByteBuf src, int length) { + return (CompositeByteBuf) super.setBytes(index, src, length); + } + + @Override + public CompositeByteBuf setBytes(int index, byte[] src) { + return (CompositeByteBuf) super.setBytes(index, src); + } + + @Override + public CompositeByteBuf setZero(int index, int length) { + return (CompositeByteBuf) super.setZero(index, length); + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst) { + return (CompositeByteBuf) super.readBytes(dst); + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst, int length) { + return (CompositeByteBuf) super.readBytes(dst, length); + } + + @Override + public CompositeByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + return (CompositeByteBuf) super.readBytes(dst, dstIndex, length); + } + + @Override + public CompositeByteBuf readBytes(byte[] dst) { + return (CompositeByteBuf) super.readBytes(dst); + } + + @Override + public CompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) { + return (CompositeByteBuf) super.readBytes(dst, dstIndex, length); + } + + @Override + public CompositeByteBuf readBytes(ByteBuffer dst) { + return (CompositeByteBuf) super.readBytes(dst); + } + + @Override + public CompositeByteBuf readBytes(OutputStream out, int length) throws IOException { + return (CompositeByteBuf) super.readBytes(out, length); + } + + @Override + public CompositeByteBuf skipBytes(int length) { + return (CompositeByteBuf) super.skipBytes(length); + } + + @Override + public CompositeByteBuf writeBoolean(boolean value) { + return (CompositeByteBuf) super.writeBoolean(value); + } + + @Override + public CompositeByteBuf writeByte(int value) { + return (CompositeByteBuf) super.writeByte(value); + } + + @Override + public CompositeByteBuf writeShort(int value) { + return (CompositeByteBuf) super.writeShort(value); + } + + @Override + public CompositeByteBuf writeMedium(int value) { + return (CompositeByteBuf) super.writeMedium(value); + } + + @Override + public CompositeByteBuf writeInt(int value) { + return (CompositeByteBuf) super.writeInt(value); + } + + @Override + public CompositeByteBuf writeLong(long value) { + return (CompositeByteBuf) super.writeLong(value); + } + + @Override + public CompositeByteBuf writeChar(int value) { + return (CompositeByteBuf) super.writeChar(value); + } + + @Override + public CompositeByteBuf writeFloat(float value) { + return (CompositeByteBuf) super.writeFloat(value); + } + + @Override + public CompositeByteBuf writeDouble(double value) { + return (CompositeByteBuf) super.writeDouble(value); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src) { + return (CompositeByteBuf) super.writeBytes(src); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src, int length) { + return (CompositeByteBuf) super.writeBytes(src, length); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + return (CompositeByteBuf) super.writeBytes(src, srcIndex, length); + } + + @Override + public CompositeByteBuf writeBytes(byte[] src) { + return (CompositeByteBuf) super.writeBytes(src); + } + + @Override + public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) { + return (CompositeByteBuf) super.writeBytes(src, srcIndex, length); + } + + @Override + public CompositeByteBuf writeBytes(ByteBuffer src) { + return (CompositeByteBuf) super.writeBytes(src); + } + + @Override + public CompositeByteBuf writeZero(int length) { + return (CompositeByteBuf) super.writeZero(length); + } + + @Override + public CompositeByteBuf retain(int increment) { + return (CompositeByteBuf) super.retain(increment); + } + + @Override + public CompositeByteBuf retain() { + return (CompositeByteBuf) super.retain(); + } + + @Override + public ByteBuffer[] nioBuffers() { + return nioBuffers(readerIndex(), readableBytes()); + } + + @Override + public CompositeByteBuf discardSomeReadBytes() { + return discardReadComponents(); + } + + @Override + protected void deallocate() { + if (freed) { + return; + } + + freed = true; + int size = components.size(); + // We're not using foreach to avoid creating an iterator. + // see https://github.com/netty/netty/issues/2642 + for (int i = 0; i < size; i++) { + components.get(i).freeIfNecessary(); + } + + if (leak != null) { + leak.close(); + } + } + + @Override + public ByteBuf unwrap() { + return null; + } +} diff --git a/common/src/common/net/buffer/DuplicatedByteBuf.java b/common/src/common/net/buffer/DuplicatedByteBuf.java new file mode 100644 index 0000000..145631c --- /dev/null +++ b/common/src/common/net/buffer/DuplicatedByteBuf.java @@ -0,0 +1,305 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + + +/** + * A derived buffer which simply forwards all data access requests to its + * parent. It is recommended to use {@link ByteBuf#duplicate()} instead + * of calling the constructor explicitly. + */ +public class DuplicatedByteBuf extends AbstractDerivedByteBuf { + + private final ByteBuf buffer; + + public DuplicatedByteBuf(ByteBuf buffer) { + super(buffer.maxCapacity()); + + if (buffer instanceof DuplicatedByteBuf) { + this.buffer = ((DuplicatedByteBuf) buffer).buffer; + } else { + this.buffer = buffer; + } + + setIndex(buffer.readerIndex(), buffer.writerIndex()); + } + + @Override + public ByteBuf unwrap() { + return buffer; + } + + @Override + public ByteBufAllocator alloc() { + return buffer.alloc(); + } + + @Override + public ByteOrder order() { + return buffer.order(); + } + + @Override + public boolean isDirect() { + return buffer.isDirect(); + } + + @Override + public int capacity() { + return buffer.capacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + buffer.capacity(newCapacity); + return this; + } + + @Override + public boolean hasArray() { + return buffer.hasArray(); + } + + @Override + public byte[] array() { + return buffer.array(); + } + + @Override + public int arrayOffset() { + return buffer.arrayOffset(); + } + + @Override + public boolean hasMemoryAddress() { + return buffer.hasMemoryAddress(); + } + + @Override + public long memoryAddress() { + return buffer.memoryAddress(); + } + + @Override + public byte getByte(int index) { + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + return buffer.getByte(index); + } + + @Override + public short getShort(int index) { + return _getShort(index); + } + + @Override + protected short _getShort(int index) { + return buffer.getShort(index); + } + + @Override + public int getUnsignedMedium(int index) { + return _getUnsignedMedium(index); + } + + @Override + protected int _getUnsignedMedium(int index) { + return buffer.getUnsignedMedium(index); + } + + @Override + public int getInt(int index) { + return _getInt(index); + } + + @Override + protected int _getInt(int index) { + return buffer.getInt(index); + } + + @Override + public long getLong(int index) { + return _getLong(index); + } + + @Override + protected long _getLong(int index) { + return buffer.getLong(index); + } + + @Override + public ByteBuf copy(int index, int length) { + return buffer.copy(index, length); + } + + @Override + public ByteBuf slice(int index, int length) { + return buffer.slice(index, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + buffer.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + buffer.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + buffer.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf setByte(int index, int value) { + _setByte(index, value); + return this; + } + + @Override + protected void _setByte(int index, int value) { + buffer.setByte(index, value); + } + + @Override + public ByteBuf setShort(int index, int value) { + _setShort(index, value); + return this; + } + + @Override + protected void _setShort(int index, int value) { + buffer.setShort(index, value); + } + + @Override + public ByteBuf setMedium(int index, int value) { + _setMedium(index, value); + return this; + } + + @Override + protected void _setMedium(int index, int value) { + buffer.setMedium(index, value); + } + + @Override + public ByteBuf setInt(int index, int value) { + _setInt(index, value); + return this; + } + + @Override + protected void _setInt(int index, int value) { + buffer.setInt(index, value); + } + + @Override + public ByteBuf setLong(int index, long value) { + _setLong(index, value); + return this; + } + + @Override + protected void _setLong(int index, long value) { + buffer.setLong(index, value); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + buffer.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + buffer.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + buffer.setBytes(index, src); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) + throws IOException { + buffer.getBytes(index, out, length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) + throws IOException { + return buffer.getBytes(index, out, length); + } + + @Override + public int setBytes(int index, InputStream in, int length) + throws IOException { + return buffer.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) + throws IOException { + return buffer.setBytes(index, in, length); + } + + @Override + public int nioBufferCount() { + return buffer.nioBufferCount(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return buffer.nioBuffers(index, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return nioBuffer(index, length); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + return buffer.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + return buffer.forEachByteDesc(index, length, processor); + } +} + diff --git a/common/src/common/net/buffer/EmptyByteBuf.java b/common/src/common/net/buffer/EmptyByteBuf.java new file mode 100644 index 0000000..1f1cb49 --- /dev/null +++ b/common/src/common/net/buffer/EmptyByteBuf.java @@ -0,0 +1,870 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; + +import common.net.util.internal.EmptyArrays; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; + +/** + * An empty {@link ByteBuf} whose capacity and maximum capacity are all {@code 0}. + */ +public final class EmptyByteBuf extends ByteBuf { + + private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocateDirect(0); + private static final long EMPTY_BYTE_BUFFER_ADDRESS; + + static { + long emptyByteBufferAddress = 0; + try { + if (PlatformDependent.hasUnsafe()) { + emptyByteBufferAddress = PlatformDependent.directBufferAddress(EMPTY_BYTE_BUFFER); + } + } catch (Throwable t) { + // Ignore + } + EMPTY_BYTE_BUFFER_ADDRESS = emptyByteBufferAddress; + } + + private final ByteBufAllocator alloc; + private final ByteOrder order; + private final String str; + private EmptyByteBuf swapped; + + public EmptyByteBuf(ByteBufAllocator alloc) { + this(alloc, ByteOrder.BIG_ENDIAN); + } + + private EmptyByteBuf(ByteBufAllocator alloc, ByteOrder order) { + if (alloc == null) { + throw new NullPointerException("alloc"); + } + + this.alloc = alloc; + this.order = order; + str = StringUtil.simpleClassName(this) + (order == ByteOrder.BIG_ENDIAN? "BE" : "LE"); + } + + @Override + public int capacity() { + return 0; + } + + @Override + public ByteBuf capacity(int newCapacity) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBufAllocator alloc() { + return alloc; + } + + @Override + public ByteOrder order() { + return order; + } + + @Override + public ByteBuf unwrap() { + return null; + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public int maxCapacity() { + return 0; + } + + @Override + public ByteBuf order(ByteOrder endianness) { + if (endianness == null) { + throw new NullPointerException("endianness"); + } + if (endianness == order()) { + return this; + } + + EmptyByteBuf swapped = this.swapped; + if (swapped != null) { + return swapped; + } + + this.swapped = swapped = new EmptyByteBuf(alloc(), endianness); + return swapped; + } + + @Override + public int readerIndex() { + return 0; + } + + @Override + public ByteBuf readerIndex(int readerIndex) { + return checkIndex(readerIndex); + } + + @Override + public int writerIndex() { + return 0; + } + + @Override + public ByteBuf writerIndex(int writerIndex) { + return checkIndex(writerIndex); + } + + @Override + public ByteBuf setIndex(int readerIndex, int writerIndex) { + checkIndex(readerIndex); + checkIndex(writerIndex); + return this; + } + + @Override + public int readableBytes() { + return 0; + } + + @Override + public int writableBytes() { + return 0; + } + + @Override + public int maxWritableBytes() { + return 0; + } + + @Override + public boolean isReadable() { + return false; + } + + @Override + public boolean isWritable() { + return false; + } + + @Override + public ByteBuf clear() { + return this; + } + + @Override + public ByteBuf markReaderIndex() { + return this; + } + + @Override + public ByteBuf resetReaderIndex() { + return this; + } + + @Override + public ByteBuf markWriterIndex() { + return this; + } + + @Override + public ByteBuf resetWriterIndex() { + return this; + } + + @Override + public ByteBuf discardReadBytes() { + return this; + } + + @Override + public ByteBuf discardSomeReadBytes() { + return this; + } + + @Override + public ByteBuf ensureWritable(int minWritableBytes) { + if (minWritableBytes < 0) { + throw new IllegalArgumentException("minWritableBytes: " + minWritableBytes + " (expected: >= 0)"); + } + if (minWritableBytes != 0) { + throw new IndexOutOfBoundsException(); + } + return this; + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + if (minWritableBytes < 0) { + throw new IllegalArgumentException("minWritableBytes: " + minWritableBytes + " (expected: >= 0)"); + } + + if (minWritableBytes == 0) { + return 0; + } + + return 1; + } + + @Override + public boolean getBoolean(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public byte getByte(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public short getUnsignedByte(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public short getShort(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getUnsignedShort(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getMedium(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getUnsignedMedium(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getInt(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public long getUnsignedInt(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public long getLong(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public char getChar(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public float getFloat(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public double getDouble(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst) { + return checkIndex(index, dst.writableBytes()); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst) { + return checkIndex(index, dst.length); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + return checkIndex(index, dst.remaining()); + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) { + return checkIndex(index, length); + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) { + checkIndex(index, length); + return 0; + } + + @Override + public ByteBuf setBoolean(int index, boolean value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setByte(int index, int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setShort(int index, int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setMedium(int index, int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setInt(int index, int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setLong(int index, long value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setChar(int index, int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setFloat(int index, float value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setDouble(int index, double value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf setBytes(int index, byte[] src) { + return checkIndex(index, src.length); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + return checkIndex(index, src.remaining()); + } + + @Override + public int setBytes(int index, InputStream in, int length) { + checkIndex(index, length); + return 0; + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) { + checkIndex(index, length); + return 0; + } + + @Override + public ByteBuf setZero(int index, int length) { + return checkIndex(index, length); + } + + @Override + public boolean readBoolean() { + throw new IndexOutOfBoundsException(); + } + + @Override + public byte readByte() { + throw new IndexOutOfBoundsException(); + } + + @Override + public short readUnsignedByte() { + throw new IndexOutOfBoundsException(); + } + + @Override + public short readShort() { + throw new IndexOutOfBoundsException(); + } + + @Override + public int readUnsignedShort() { + throw new IndexOutOfBoundsException(); + } + + @Override + public int readMedium() { + throw new IndexOutOfBoundsException(); + } + + @Override + public int readUnsignedMedium() { + throw new IndexOutOfBoundsException(); + } + + @Override + public int readInt() { + throw new IndexOutOfBoundsException(); + } + + @Override + public long readUnsignedInt() { + throw new IndexOutOfBoundsException(); + } + + @Override + public long readLong() { + throw new IndexOutOfBoundsException(); + } + + @Override + public char readChar() { + throw new IndexOutOfBoundsException(); + } + + @Override + public float readFloat() { + throw new IndexOutOfBoundsException(); + } + + @Override + public double readDouble() { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf readBytes(int length) { + return checkLength(length); + } + + @Override + public ByteBuf readSlice(int length) { + return checkLength(length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst) { + return checkLength(dst.writableBytes()); + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int length) { + return checkLength(length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + return checkLength(length); + } + + @Override + public ByteBuf readBytes(byte[] dst) { + return checkLength(dst.length); + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + return checkLength(length); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + return checkLength(dst.remaining()); + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) { + return checkLength(length); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) { + checkLength(length); + return 0; + } + + @Override + public ByteBuf skipBytes(int length) { + return checkLength(length); + } + + @Override + public ByteBuf writeBoolean(boolean value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeByte(int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeShort(int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeMedium(int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeInt(int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeLong(long value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeChar(int value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeFloat(float value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeDouble(double value) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeBytes(ByteBuf src) { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int length) { + return checkLength(length); + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + return checkLength(length); + } + + @Override + public ByteBuf writeBytes(byte[] src) { + return checkLength(src.length); + } + + @Override + public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { + return checkLength(length); + } + + @Override + public ByteBuf writeBytes(ByteBuffer src) { + return checkLength(src.remaining()); + } + + @Override + public int writeBytes(InputStream in, int length) { + checkLength(length); + return 0; + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) { + checkLength(length); + return 0; + } + + @Override + public ByteBuf writeZero(int length) { + return checkLength(length); + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + checkIndex(fromIndex); + checkIndex(toIndex); + return -1; + } + + @Override + public int bytesBefore(byte value) { + return -1; + } + + @Override + public int bytesBefore(int length, byte value) { + checkLength(length); + return -1; + } + + @Override + public int bytesBefore(int index, int length, byte value) { + checkIndex(index, length); + return -1; + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + return -1; + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + checkIndex(index, length); + return -1; + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + return -1; + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + checkIndex(index, length); + return -1; + } + + @Override + public ByteBuf copy() { + return this; + } + + @Override + public ByteBuf copy(int index, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf slice() { + return this; + } + + @Override + public ByteBuf slice(int index, int length) { + return checkIndex(index, length); + } + + @Override + public ByteBuf duplicate() { + return this; + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer nioBuffer() { + return EMPTY_BYTE_BUFFER; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + return nioBuffer(); + } + + @Override + public ByteBuffer[] nioBuffers() { + return new ByteBuffer[] { EMPTY_BYTE_BUFFER }; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + checkIndex(index, length); + return nioBuffers(); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return EMPTY_BYTE_BUFFER; + } + + @Override + public boolean hasArray() { + return true; + } + + @Override + public byte[] array() { + return EmptyArrays.EMPTY_BYTES; + } + + @Override + public int arrayOffset() { + return 0; + } + + @Override + public boolean hasMemoryAddress() { + return EMPTY_BYTE_BUFFER_ADDRESS != 0; + } + + @Override + public long memoryAddress() { + if (hasMemoryAddress()) { + return EMPTY_BYTE_BUFFER_ADDRESS; + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public String toString(Charset charset) { + return ""; + } + + @Override + public String toString(int index, int length, Charset charset) { + checkIndex(index, length); + return toString(charset); + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ByteBuf && !((ByteBuf) obj).isReadable(); + } + + @Override + public int compareTo(ByteBuf buffer) { + return buffer.isReadable()? -1 : 0; + } + + @Override + public String toString() { + return str; + } + + @Override + public boolean isReadable(int size) { + return false; + } + + @Override + public boolean isWritable(int size) { + return false; + } + + @Override + public int refCnt() { + return 1; + } + + @Override + public ByteBuf retain() { + return this; + } + + @Override + public ByteBuf retain(int increment) { + return this; + } + + @Override + public boolean release() { + return false; + } + + @Override + public boolean release(int decrement) { + return false; + } + + private ByteBuf checkIndex(int index) { + if (index != 0) { + throw new IndexOutOfBoundsException(); + } + return this; + } + + private ByteBuf checkIndex(int index, int length) { + if (length < 0) { + throw new IllegalArgumentException("length: " + length); + } + if (index != 0 || length != 0) { + throw new IndexOutOfBoundsException(); + } + return this; + } + + private ByteBuf checkLength(int length) { + if (length < 0) { + throw new IllegalArgumentException("length: " + length + " (expected: >= 0)"); + } + if (length != 0) { + throw new IndexOutOfBoundsException(); + } + return this; + } +} diff --git a/common/src/common/net/buffer/PoolArena.java b/common/src/common/net/buffer/PoolArena.java new file mode 100644 index 0000000..ac2c5f6 --- /dev/null +++ b/common/src/common/net/buffer/PoolArena.java @@ -0,0 +1,477 @@ +/* + * 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 common.net.buffer; + +import java.nio.ByteBuffer; + +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; + +abstract class PoolArena { + + static final int numTinySubpagePools = 512 >>> 4; + + final PooledByteBufAllocator parent; + + private final int maxOrder; + final int pageSize; + final int pageShifts; + final int chunkSize; + final int subpageOverflowMask; + final int numSmallSubpagePools; + private final PoolSubpage[] tinySubpagePools; + private final PoolSubpage[] smallSubpagePools; + + private final PoolChunkList q050; + private final PoolChunkList q025; + private final PoolChunkList q000; + private final PoolChunkList qInit; + private final PoolChunkList q075; + private final PoolChunkList q100; + + // TODO: Test if adding padding helps under contention + //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) { + this.parent = parent; + this.pageSize = pageSize; + this.maxOrder = maxOrder; + this.pageShifts = pageShifts; + this.chunkSize = chunkSize; + subpageOverflowMask = ~(pageSize - 1); + tinySubpagePools = newSubpagePoolArray(numTinySubpagePools); + for (int i = 0; i < tinySubpagePools.length; i ++) { + tinySubpagePools[i] = newSubpagePoolHead(pageSize); + } + + numSmallSubpagePools = pageShifts - 9; + smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools); + for (int i = 0; i < smallSubpagePools.length; i ++) { + smallSubpagePools[i] = newSubpagePoolHead(pageSize); + } + + q100 = new PoolChunkList(this, null, 100, Integer.MAX_VALUE); + q075 = new PoolChunkList(this, q100, 75, 100); + q050 = new PoolChunkList(this, q075, 50, 100); + q025 = new PoolChunkList(this, q050, 25, 75); + q000 = new PoolChunkList(this, q025, 1, 50); + qInit = new PoolChunkList(this, q000, Integer.MIN_VALUE, 25); + + q100.prevList = q075; + q075.prevList = q050; + q050.prevList = q025; + q025.prevList = q000; + q000.prevList = null; + qInit.prevList = qInit; + } + + private PoolSubpage newSubpagePoolHead(int pageSize) { + PoolSubpage head = new PoolSubpage(pageSize); + head.prev = head; + head.next = head; + return head; + } + + + private PoolSubpage[] newSubpagePoolArray(int size) { + return new PoolSubpage[size]; + } + + abstract boolean isDirect(); + + PooledByteBuf allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { + PooledByteBuf buf = newByteBuf(maxCapacity); + allocate(cache, buf, reqCapacity); + return buf; + } + + static int tinyIdx(int normCapacity) { + return normCapacity >>> 4; + } + + static int smallIdx(int normCapacity) { + int tableIdx = 0; + int i = normCapacity >>> 10; + while (i != 0) { + i >>>= 1; + tableIdx ++; + } + return tableIdx; + } + + // capacity < pageSize + boolean isTinyOrSmall(int normCapacity) { + return (normCapacity & subpageOverflowMask) == 0; + } + + // normCapacity < 512 + static boolean isTiny(int normCapacity) { + return (normCapacity & 0xFFFFFE00) == 0; + } + + private void allocate(PoolThreadCache cache, PooledByteBuf buf, final int reqCapacity) { + final int normCapacity = normalizeCapacity(reqCapacity); + if (isTinyOrSmall(normCapacity)) { // capacity < pageSize + int tableIdx; + PoolSubpage[] table; + if (isTiny(normCapacity)) { // < 512 + if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { + // was able to allocate out of the cache so move on + return; + } + tableIdx = tinyIdx(normCapacity); + table = tinySubpagePools; + } else { + if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { + // was able to allocate out of the cache so move on + return; + } + tableIdx = smallIdx(normCapacity); + table = smallSubpagePools; + } + + synchronized (this) { + final PoolSubpage head = table[tableIdx]; + final PoolSubpage s = head.next; + if (s != head) { + assert s.doNotDestroy && s.elemSize == normCapacity; + long handle = s.allocate(); + assert handle >= 0; + s.chunk.initBufWithSubpage(buf, handle, reqCapacity); + return; + } + } + } else if (normCapacity <= chunkSize) { + if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { + // was able to allocate out of the cache so move on + return; + } + } else { + // Huge allocations are never served via the cache so just call allocateHuge + allocateHuge(buf, reqCapacity); + return; + } + allocateNormal(buf, reqCapacity, normCapacity); + } + + private synchronized void allocateNormal(PooledByteBuf buf, int reqCapacity, int normCapacity) { + if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) || + q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) || + q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity)) { + return; + } + + // Add a new chunk. + PoolChunk c = newChunk(pageSize, maxOrder, pageShifts, chunkSize); + long handle = c.allocate(normCapacity); + assert handle > 0; + c.initBuf(buf, handle, reqCapacity); + qInit.add(c); + } + + private void allocateHuge(PooledByteBuf buf, int reqCapacity) { + buf.initUnpooled(newUnpooledChunk(reqCapacity), reqCapacity); + } + + void free(PoolChunk chunk, long handle, int normCapacity) { + if (chunk.unpooled) { + destroyChunk(chunk); + } else { + PoolThreadCache cache = parent.threadCache.get(); + if (cache.add(this, chunk, handle, normCapacity)) { + // cached so not free it. + return; + } + synchronized (this) { + chunk.parent.free(chunk, handle); + } + } + } + + PoolSubpage findSubpagePoolHead(int elemSize) { + int tableIdx; + PoolSubpage[] table; + if (isTiny(elemSize)) { // < 512 + tableIdx = elemSize >>> 4; + table = tinySubpagePools; + } else { + tableIdx = 0; + elemSize >>>= 10; + while (elemSize != 0) { + elemSize >>>= 1; + tableIdx ++; + } + table = smallSubpagePools; + } + + return table[tableIdx]; + } + + int normalizeCapacity(int reqCapacity) { + if (reqCapacity < 0) { + throw new IllegalArgumentException("capacity: " + reqCapacity + " (expected: 0+)"); + } + if (reqCapacity >= chunkSize) { + return reqCapacity; + } + + if (!isTiny(reqCapacity)) { // >= 512 + // Doubled + + int normalizedCapacity = reqCapacity; + normalizedCapacity --; + normalizedCapacity |= normalizedCapacity >>> 1; + normalizedCapacity |= normalizedCapacity >>> 2; + normalizedCapacity |= normalizedCapacity >>> 4; + normalizedCapacity |= normalizedCapacity >>> 8; + normalizedCapacity |= normalizedCapacity >>> 16; + normalizedCapacity ++; + + if (normalizedCapacity < 0) { + normalizedCapacity >>>= 1; + } + + return normalizedCapacity; + } + + // Quantum-spaced + if ((reqCapacity & 15) == 0) { + return reqCapacity; + } + + return (reqCapacity & ~15) + 16; + } + + void reallocate(PooledByteBuf buf, int newCapacity, boolean freeOldMemory) { + if (newCapacity < 0 || newCapacity > buf.maxCapacity()) { + throw new IllegalArgumentException("newCapacity: " + newCapacity); + } + + int oldCapacity = buf.length; + if (oldCapacity == newCapacity) { + return; + } + + PoolChunk oldChunk = buf.chunk; + long oldHandle = buf.handle; + T oldMemory = buf.memory; + int oldOffset = buf.offset; + int oldMaxLength = buf.maxLength; + int readerIndex = buf.readerIndex(); + int writerIndex = buf.writerIndex(); + + allocate(parent.threadCache.get(), buf, newCapacity); + if (newCapacity > oldCapacity) { + memoryCopy( + oldMemory, oldOffset, + buf.memory, buf.offset, oldCapacity); + } else if (newCapacity < oldCapacity) { + if (readerIndex < newCapacity) { + if (writerIndex > newCapacity) { + writerIndex = newCapacity; + } + memoryCopy( + oldMemory, oldOffset + readerIndex, + buf.memory, buf.offset + readerIndex, writerIndex - readerIndex); + } else { + readerIndex = writerIndex = newCapacity; + } + } + + buf.setIndex(readerIndex, writerIndex); + + if (freeOldMemory) { + free(oldChunk, oldHandle, oldMaxLength); + } + } + + protected abstract PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize); + protected abstract PoolChunk newUnpooledChunk(int capacity); + protected abstract PooledByteBuf newByteBuf(int maxCapacity); + protected abstract void memoryCopy(T src, int srcOffset, T dst, int dstOffset, int length); + protected abstract void destroyChunk(PoolChunk chunk); + + public synchronized String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("Chunk(s) at 0~25%:"); + buf.append(StringUtil.NEWLINE); + buf.append(qInit); + buf.append(StringUtil.NEWLINE); + buf.append("Chunk(s) at 0~50%:"); + buf.append(StringUtil.NEWLINE); + buf.append(q000); + buf.append(StringUtil.NEWLINE); + buf.append("Chunk(s) at 25~75%:"); + buf.append(StringUtil.NEWLINE); + buf.append(q025); + buf.append(StringUtil.NEWLINE); + buf.append("Chunk(s) at 50~100%:"); + buf.append(StringUtil.NEWLINE); + buf.append(q050); + buf.append(StringUtil.NEWLINE); + buf.append("Chunk(s) at 75~100%:"); + buf.append(StringUtil.NEWLINE); + buf.append(q075); + buf.append(StringUtil.NEWLINE); + buf.append("Chunk(s) at 100%:"); + buf.append(StringUtil.NEWLINE); + buf.append(q100); + buf.append(StringUtil.NEWLINE); + buf.append("tiny subpages:"); + for (int i = 1; i < tinySubpagePools.length; i ++) { + PoolSubpage head = tinySubpagePools[i]; + if (head.next == head) { + continue; + } + + buf.append(StringUtil.NEWLINE); + buf.append(i); + buf.append(": "); + PoolSubpage s = head.next; + for (;;) { + buf.append(s); + s = s.next; + if (s == head) { + break; + } + } + } + buf.append(StringUtil.NEWLINE); + buf.append("small subpages:"); + for (int i = 1; i < smallSubpagePools.length; i ++) { + PoolSubpage head = smallSubpagePools[i]; + if (head.next == head) { + continue; + } + + buf.append(StringUtil.NEWLINE); + buf.append(i); + buf.append(": "); + PoolSubpage s = head.next; + for (;;) { + buf.append(s); + s = s.next; + if (s == head) { + break; + } + } + } + buf.append(StringUtil.NEWLINE); + + return buf.toString(); + } + + static final class HeapArena extends PoolArena { + + HeapArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) { + super(parent, pageSize, maxOrder, pageShifts, chunkSize); + } + + @Override + boolean isDirect() { + return false; + } + + @Override + protected PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) { + return new PoolChunk(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize); + } + + @Override + protected PoolChunk newUnpooledChunk(int capacity) { + return new PoolChunk(this, new byte[capacity], capacity); + } + + @Override + protected void destroyChunk(PoolChunk chunk) { + // Rely on GC. + } + + @Override + protected PooledByteBuf newByteBuf(int maxCapacity) { + return PooledHeapByteBuf.newInstance(maxCapacity); + } + + @Override + protected void memoryCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) { + if (length == 0) { + return; + } + + System.arraycopy(src, srcOffset, dst, dstOffset, length); + } + } + + static final class DirectArena extends PoolArena { + + private static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe(); + + DirectArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) { + super(parent, pageSize, maxOrder, pageShifts, chunkSize); + } + + @Override + boolean isDirect() { + return true; + } + + @Override + protected PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) { + return new PoolChunk( + this, ByteBuffer.allocateDirect(chunkSize), pageSize, maxOrder, pageShifts, chunkSize); + } + + @Override + protected PoolChunk newUnpooledChunk(int capacity) { + return new PoolChunk(this, ByteBuffer.allocateDirect(capacity), capacity); + } + + @Override + protected void destroyChunk(PoolChunk chunk) { + PlatformDependent.freeDirectBuffer(chunk.memory); + } + + @Override + protected PooledByteBuf newByteBuf(int maxCapacity) { + if (HAS_UNSAFE) { + return PooledUnsafeDirectByteBuf.newInstance(maxCapacity); + } else { + return PooledDirectByteBuf.newInstance(maxCapacity); + } + } + + @Override + protected void memoryCopy(ByteBuffer src, int srcOffset, ByteBuffer dst, int dstOffset, int length) { + if (length == 0) { + return; + } + + if (HAS_UNSAFE) { + PlatformDependent.copyMemory( + PlatformDependent.directBufferAddress(src) + srcOffset, + PlatformDependent.directBufferAddress(dst) + dstOffset, length); + } else { + // We must duplicate the NIO buffers because they may be accessed by other Netty buffers. + src = src.duplicate(); + dst = dst.duplicate(); + src.position(srcOffset).limit(srcOffset + length); + dst.position(dstOffset); + dst.put(src); + } + } + } +} diff --git a/common/src/common/net/buffer/PoolChunk.java b/common/src/common/net/buffer/PoolChunk.java new file mode 100644 index 0000000..87234ec --- /dev/null +++ b/common/src/common/net/buffer/PoolChunk.java @@ -0,0 +1,431 @@ +/* + * 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 common.net.buffer; + +/** + * Description of algorithm for PageRun/PoolSubpage allocation from PoolChunk + * + * Notation: The following terms are important to understand the code + * > page - a page is the smallest unit of memory chunk that can be allocated + * > chunk - a chunk is a collection of pages + * > in this code chunkSize = 2^{maxOrder} * pageSize + * + * To begin we allocate a byte array of size = chunkSize + * Whenever a ByteBuf of given size needs to be created we search for the first position + * in the byte array that has enough empty space to accommodate the requested size and + * return a (long) handle that encodes this offset information, (this memory segment is then + * marked as reserved so it is always used by exactly one ByteBuf and no more) + * + * For simplicity all sizes are normalized according to PoolArena#normalizeCapacity method + * This ensures that when we request for memory segments of size >= pageSize the normalizedCapacity + * equals the next nearest power of 2 + * + * To search for the first offset in chunk that has at least requested size available we construct a + * complete balanced binary tree and store it in an array (just like heaps) - memoryMap + * + * The tree looks like this (the size of each node being mentioned in the parenthesis) + * + * depth=0 1 node (chunkSize) + * depth=1 2 nodes (chunkSize/2) + * .. + * .. + * depth=d 2^d nodes (chunkSize/2^d) + * .. + * depth=maxOrder 2^maxOrder nodes (chunkSize/2^{maxOrder} = pageSize) + * + * depth=maxOrder is the last level and the leafs consist of pages + * + * With this tree available searching in chunkArray translates like this: + * To allocate a memory segment of size chunkSize/2^k we search for the first node (from left) at height k + * which is unused + * + * Algorithm: + * ---------- + * Encode the tree in memoryMap with the notation + * memoryMap[id] = x => in the subtree rooted at id, the first node that is free to be allocated + * is at depth x (counted from depth=0) i.e., at depths [depth_of_id, x), there is no node that is free + * + * As we allocate & free nodes, we update values stored in memoryMap so that the property is maintained + * + * Initialization - + * In the beginning we construct the memoryMap array by storing the depth of a node at each node + * i.e., memoryMap[id] = depth_of_id + * + * Observations: + * ------------- + * 1) memoryMap[id] = depth_of_id => it is free / unallocated + * 2) memoryMap[id] > depth_of_id => at least one of its child nodes is allocated, so we cannot allocate it, but + * some of its children can still be allocated based on their availability + * 3) memoryMap[id] = maxOrder + 1 => the node is fully allocated & thus none of its children can be allocated, it + * is thus marked as unusable + * + * Algorithm: [allocateNode(d) => we want to find the first node (from left) at height h that can be allocated] + * ---------- + * 1) start at root (i.e., depth = 0 or id = 1) + * 2) if memoryMap[1] > d => cannot be allocated from this chunk + * 3) if left node value <= h; we can allocate from left subtree so move to left and repeat until found + * 4) else try in right subtree + * + * Algorithm: [allocateRun(size)] + * ---------- + * 1) Compute d = log_2(chunkSize/size) + * 2) Return allocateNode(d) + * + * Algorithm: [allocateSubpage(size)] + * ---------- + * 1) use allocateNode(maxOrder) to find an empty (i.e., unused) leaf (i.e., page) + * 2) use this handle to construct the PoolSubpage object or if it already exists just call init(normCapacity) + * note that this PoolSubpage object is added to subpagesPool in the PoolArena when we init() it + * + * Note: + * ----- + * In the implementation for improving cache coherence, + * we store 2 pieces of information (i.e, 2 byte vals) as a short value in memoryMap + * + * memoryMap[id]= (depth_of_id, x) + * where as per convention defined above + * the second value (i.e, x) indicates that the first node which is free to be allocated is at depth x (from root) + */ + +final class PoolChunk { + + final PoolArena arena; + final T memory; + final boolean unpooled; + + private final byte[] memoryMap; + private final byte[] depthMap; + private final PoolSubpage[] subpages; + /** Used to determine if the requested capacity is equal to or greater than pageSize. */ + private final int subpageOverflowMask; + private final int pageSize; + private final int pageShifts; + private final int maxOrder; + private final int chunkSize; + private final int log2ChunkSize; + private final int maxSubpageAllocs; + /** Used to mark memory as unusable */ + private final byte unusable; + + private int freeBytes; + + PoolChunkList parent; + PoolChunk prev; + PoolChunk next; + + // TODO: Test if adding padding helps under contention + //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + PoolChunk(PoolArena arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) { + unpooled = false; + this.arena = arena; + this.memory = memory; + this.pageSize = pageSize; + this.pageShifts = pageShifts; + this.maxOrder = maxOrder; + this.chunkSize = chunkSize; + unusable = (byte) (maxOrder + 1); + log2ChunkSize = log2(chunkSize); + subpageOverflowMask = ~(pageSize - 1); + freeBytes = chunkSize; + + assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder; + maxSubpageAllocs = 1 << maxOrder; + + // Generate the memory map. + memoryMap = new byte[maxSubpageAllocs << 1]; + depthMap = new byte[memoryMap.length]; + int memoryMapIndex = 1; + for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time + int depth = 1 << d; + for (int p = 0; p < depth; ++ p) { + // in each level traverse left to right and set value to the depth of subtree + memoryMap[memoryMapIndex] = (byte) d; + depthMap[memoryMapIndex] = (byte) d; + memoryMapIndex ++; + } + } + + subpages = newSubpageArray(maxSubpageAllocs); + } + + /** Creates a special chunk that is not pooled. */ + PoolChunk(PoolArena arena, T memory, int size) { + unpooled = true; + this.arena = arena; + this.memory = memory; + memoryMap = null; + depthMap = null; + subpages = null; + subpageOverflowMask = 0; + pageSize = 0; + pageShifts = 0; + maxOrder = 0; + unusable = (byte) (maxOrder + 1); + chunkSize = size; + log2ChunkSize = log2(chunkSize); + maxSubpageAllocs = 0; + } + + + private PoolSubpage[] newSubpageArray(int size) { + return new PoolSubpage[size]; + } + + int usage() { + final int freeBytes = this.freeBytes; + if (freeBytes == 0) { + return 100; + } + + int freePercentage = (int) (freeBytes * 100L / chunkSize); + if (freePercentage == 0) { + return 99; + } + return 100 - freePercentage; + } + + long allocate(int normCapacity) { + if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize + return allocateRun(normCapacity); + } else { + return allocateSubpage(normCapacity); + } + } + + /** + * Update method used by allocate + * This is triggered only when a successor is allocated and all its predecessors + * need to update their state + * The minimal depth at which subtree rooted at id has some free space + * + * @param id id + */ + private void updateParentsAlloc(int id) { + while (id > 1) { + int parentId = id >>> 1; + byte val1 = value(id); + byte val2 = value(id ^ 1); + byte val = val1 < val2 ? val1 : val2; + setValue(parentId, val); + id = parentId; + } + } + + /** + * Update method used by free + * This needs to handle the special case when both children are completely free + * in which case parent be directly allocated on request of size = child-size * 2 + * + * @param id id + */ + private void updateParentsFree(int id) { + int logChild = depth(id) + 1; + while (id > 1) { + int parentId = id >>> 1; + byte val1 = value(id); + byte val2 = value(id ^ 1); + logChild -= 1; // in first iteration equals log, subsequently reduce 1 from logChild as we traverse up + + if (val1 == logChild && val2 == logChild) { + setValue(parentId, (byte) (logChild - 1)); + } else { + byte val = val1 < val2 ? val1 : val2; + setValue(parentId, val); + } + + id = parentId; + } + } + + /** + * Algorithm to allocate an index in memoryMap when we query for a free node + * at depth d + * + * @param d depth + * @return index in memoryMap + */ + private int allocateNode(int d) { + int id = 1; + int initial = - (1 << d); // has last d bits = 0 and rest all = 1 + byte val = value(id); + if (val > d) { // unusable + return -1; + } + while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0 + id <<= 1; + val = value(id); + if (val > d) { + id ^= 1; + val = value(id); + } + } + byte value = value(id); + assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d", + value, id & initial, d); + setValue(id, unusable); // mark as unusable + updateParentsAlloc(id); + return id; + } + + /** + * Allocate a run of pages (>=1) + * + * @param normCapacity normalized capacity + * @return index in memoryMap + */ + private long allocateRun(int normCapacity) { + int d = maxOrder - (log2(normCapacity) - pageShifts); + int id = allocateNode(d); + if (id < 0) { + return id; + } + freeBytes -= runLength(id); + return id; + } + + /** + * Create/ initialize a new PoolSubpage of normCapacity + * Any PoolSubpage created/ initialized here is added to subpage pool in the PoolArena that owns this PoolChunk + * + * @param normCapacity normalized capacity + * @return index in memoryMap + */ + private long allocateSubpage(int normCapacity) { + int d = maxOrder; // subpages are only be allocated from pages i.e., leaves + int id = allocateNode(d); + if (id < 0) { + return id; + } + + final PoolSubpage[] subpages = this.subpages; + final int pageSize = this.pageSize; + + freeBytes -= pageSize; + + int subpageIdx = subpageIdx(id); + PoolSubpage subpage = subpages[subpageIdx]; + if (subpage == null) { + subpage = new PoolSubpage(this, id, runOffset(id), pageSize, normCapacity); + subpages[subpageIdx] = subpage; + } else { + subpage.init(normCapacity); + } + return subpage.allocate(); + } + + /** + * Free a subpage or a run of pages + * When a subpage is freed from PoolSubpage, it might be added back to subpage pool of the owning PoolArena + * If the subpage pool in PoolArena has at least one other PoolSubpage of given elemSize, we can + * completely free the owning Page so it is available for subsequent allocations + * + * @param handle handle to free + */ + void free(long handle) { + int memoryMapIdx = (int) handle; + int bitmapIdx = (int) (handle >>> Integer.SIZE); + + if (bitmapIdx != 0) { // free a subpage + PoolSubpage subpage = subpages[subpageIdx(memoryMapIdx)]; + assert subpage != null && subpage.doNotDestroy; + if (subpage.free(bitmapIdx & 0x3FFFFFFF)) { + return; + } + } + freeBytes += runLength(memoryMapIdx); + setValue(memoryMapIdx, depth(memoryMapIdx)); + updateParentsFree(memoryMapIdx); + } + + void initBuf(PooledByteBuf buf, long handle, int reqCapacity) { + int memoryMapIdx = (int) handle; + int bitmapIdx = (int) (handle >>> Integer.SIZE); + if (bitmapIdx == 0) { + byte val = value(memoryMapIdx); + assert val == unusable : String.valueOf(val); + buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx)); + } else { + initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity); + } + } + + void initBufWithSubpage(PooledByteBuf buf, long handle, int reqCapacity) { + initBufWithSubpage(buf, handle, (int) (handle >>> Integer.SIZE), reqCapacity); + } + + private void initBufWithSubpage(PooledByteBuf buf, long handle, int bitmapIdx, int reqCapacity) { + assert bitmapIdx != 0; + + int memoryMapIdx = (int) handle; + + PoolSubpage subpage = subpages[subpageIdx(memoryMapIdx)]; + assert subpage.doNotDestroy; + assert reqCapacity <= subpage.elemSize; + + buf.init( + this, handle, + runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize, reqCapacity, subpage.elemSize); + } + + private byte value(int id) { + return memoryMap[id]; + } + + private void setValue(int id, byte val) { + memoryMap[id] = val; + } + + private byte depth(int id) { + return depthMap[id]; + } + + private static int log2(int val) { + // compute the (0-based, with lsb = 0) position of highest set bit i.e, log2 + return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(val); + } + + private int runLength(int id) { + // represents the size in #bytes supported by node 'id' in the tree + return 1 << log2ChunkSize - depth(id); + } + + private int runOffset(int id) { + // represents the 0-based offset in #bytes from start of the byte-array chunk + int shift = id ^ 1 << depth(id); + return shift * runLength(id); + } + + private int subpageIdx(int memoryMapIdx) { + return memoryMapIdx ^ maxSubpageAllocs; // remove highest set bit, to get offset + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("Chunk("); + buf.append(Integer.toHexString(System.identityHashCode(this))); + buf.append(": "); + buf.append(usage()); + buf.append("%, "); + buf.append(chunkSize - freeBytes); + buf.append('/'); + buf.append(chunkSize); + buf.append(')'); + return buf.toString(); + } +} diff --git a/common/src/common/net/buffer/PoolChunkList.java b/common/src/common/net/buffer/PoolChunkList.java new file mode 100644 index 0000000..f0a5f40 --- /dev/null +++ b/common/src/common/net/buffer/PoolChunkList.java @@ -0,0 +1,129 @@ +/* + * 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 common.net.buffer; + +import common.net.util.internal.StringUtil; + +final class PoolChunkList { + private final PoolArena arena; + private final PoolChunkList nextList; + PoolChunkList prevList; + + private final int minUsage; + private final int maxUsage; + + private PoolChunk head; + + // TODO: Test if adding padding helps under contention + //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + PoolChunkList(PoolArena arena, PoolChunkList nextList, int minUsage, int maxUsage) { + this.arena = arena; + this.nextList = nextList; + this.minUsage = minUsage; + this.maxUsage = maxUsage; + } + + boolean allocate(PooledByteBuf buf, int reqCapacity, int normCapacity) { + if (head == null) { + return false; + } + + for (PoolChunk cur = head;;) { + long handle = cur.allocate(normCapacity); + if (handle < 0) { + cur = cur.next; + if (cur == null) { + return false; + } + } else { + cur.initBuf(buf, handle, reqCapacity); + if (cur.usage() >= maxUsage) { + remove(cur); + nextList.add(cur); + } + return true; + } + } + } + + void free(PoolChunk chunk, long handle) { + chunk.free(handle); + if (chunk.usage() < minUsage) { + remove(chunk); + if (prevList == null) { + assert chunk.usage() == 0; + arena.destroyChunk(chunk); + } else { + prevList.add(chunk); + } + } + } + + void add(PoolChunk chunk) { + if (chunk.usage() >= maxUsage) { + nextList.add(chunk); + return; + } + + chunk.parent = this; + if (head == null) { + head = chunk; + chunk.prev = null; + chunk.next = null; + } else { + chunk.prev = null; + chunk.next = head; + head.prev = chunk; + head = chunk; + } + } + + private void remove(PoolChunk cur) { + if (cur == head) { + head = cur.next; + if (head != null) { + head.prev = null; + } + } else { + PoolChunk next = cur.next; + cur.prev.next = next; + if (next != null) { + next.prev = cur.prev; + } + } + } + + @Override + public String toString() { + if (head == null) { + return "none"; + } + + StringBuilder buf = new StringBuilder(); + for (PoolChunk cur = head;;) { + buf.append(cur); + cur = cur.next; + if (cur == null) { + break; + } + buf.append(StringUtil.NEWLINE); + } + + return buf.toString(); + } +} diff --git a/common/src/common/net/buffer/PoolSubpage.java b/common/src/common/net/buffer/PoolSubpage.java new file mode 100644 index 0000000..ae643c7 --- /dev/null +++ b/common/src/common/net/buffer/PoolSubpage.java @@ -0,0 +1,213 @@ +/* + * 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 common.net.buffer; + +final class PoolSubpage { + + final PoolChunk chunk; + private final int memoryMapIdx; + private final int runOffset; + private final int pageSize; + private final long[] bitmap; + + PoolSubpage prev; + PoolSubpage next; + + boolean doNotDestroy; + int elemSize; + private int maxNumElems; + private int bitmapLength; + private int nextAvail; + private int numAvail; + + // TODO: Test if adding padding helps under contention + //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + /** Special constructor that creates a linked list head */ + PoolSubpage(int pageSize) { + chunk = null; + memoryMapIdx = -1; + runOffset = -1; + elemSize = -1; + this.pageSize = pageSize; + bitmap = null; + } + + PoolSubpage(PoolChunk chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) { + this.chunk = chunk; + this.memoryMapIdx = memoryMapIdx; + this.runOffset = runOffset; + this.pageSize = pageSize; + bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64 + init(elemSize); + } + + void init(int elemSize) { + doNotDestroy = true; + this.elemSize = elemSize; + if (elemSize != 0) { + maxNumElems = numAvail = pageSize / elemSize; + nextAvail = 0; + bitmapLength = maxNumElems >>> 6; + if ((maxNumElems & 63) != 0) { + bitmapLength ++; + } + + for (int i = 0; i < bitmapLength; i ++) { + bitmap[i] = 0; + } + } + + addToPool(); + } + + /** + * Returns the bitmap index of the subpage allocation. + */ + long allocate() { + if (elemSize == 0) { + return toHandle(0); + } + + if (numAvail == 0 || !doNotDestroy) { + return -1; + } + + final int bitmapIdx = getNextAvail(); + int q = bitmapIdx >>> 6; + int r = bitmapIdx & 63; + assert (bitmap[q] >>> r & 1) == 0; + bitmap[q] |= 1L << r; + + if (-- numAvail == 0) { + removeFromPool(); + } + + return toHandle(bitmapIdx); + } + + /** + * @return {@code true} if this subpage is in use. + * {@code false} if this subpage is not used by its chunk and thus it's OK to be released. + */ + boolean free(int bitmapIdx) { + + if (elemSize == 0) { + return true; + } + + int q = bitmapIdx >>> 6; + int r = bitmapIdx & 63; + assert (bitmap[q] >>> r & 1) != 0; + bitmap[q] ^= 1L << r; + + setNextAvail(bitmapIdx); + + if (numAvail ++ == 0) { + addToPool(); + return true; + } + + if (numAvail != maxNumElems) { + return true; + } else { + // Subpage not in use (numAvail == maxNumElems) + if (prev == next) { + // Do not remove if this subpage is the only one left in the pool. + return true; + } + + // Remove this subpage from the pool if there are other subpages left in the pool. + doNotDestroy = false; + removeFromPool(); + return false; + } + } + + private void addToPool() { + PoolSubpage head = chunk.arena.findSubpagePoolHead(elemSize); + assert prev == null && next == null; + prev = head; + next = head.next; + next.prev = this; + head.next = this; + } + + private void removeFromPool() { + assert prev != null && next != null; + prev.next = next; + next.prev = prev; + next = null; + prev = null; + } + + private void setNextAvail(int bitmapIdx) { + nextAvail = bitmapIdx; + } + + private int getNextAvail() { + int nextAvail = this.nextAvail; + if (nextAvail >= 0) { + this.nextAvail = -1; + return nextAvail; + } + return findNextAvail(); + } + + private int findNextAvail() { + final long[] bitmap = this.bitmap; + final int bitmapLength = this.bitmapLength; + for (int i = 0; i < bitmapLength; i ++) { + long bits = bitmap[i]; + if (~bits != 0) { + return findNextAvail0(i, bits); + } + } + return -1; + } + + private int findNextAvail0(int i, long bits) { + final int maxNumElems = this.maxNumElems; + final int baseVal = i << 6; + + for (int j = 0; j < 64; j ++) { + if ((bits & 1) == 0) { + int val = baseVal | j; + if (val < maxNumElems) { + return val; + } else { + break; + } + } + bits >>>= 1; + } + return -1; + } + + private long toHandle(int bitmapIdx) { + return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx; + } + + public String toString() { + if (!doNotDestroy) { + return "(" + memoryMapIdx + ": not in use)"; + } + + return String.valueOf('(') + memoryMapIdx + ": " + (maxNumElems - numAvail) + '/' + maxNumElems + + ", offset: " + runOffset + ", length: " + pageSize + ", elemSize: " + elemSize + ')'; + } +} diff --git a/common/src/common/net/buffer/PoolThreadCache.java b/common/src/common/net/buffer/PoolThreadCache.java new file mode 100644 index 0000000..1a41c18 --- /dev/null +++ b/common/src/common/net/buffer/PoolThreadCache.java @@ -0,0 +1,485 @@ +/* + * 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 common.net.buffer; + + +import java.nio.ByteBuffer; + +import common.net.util.ThreadDeathWatcher; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Acts a Thread cache for allocations. This implementation is moduled after + * jemalloc and the descripted + * technics of Scalable memory allocation using jemalloc. + */ +final class PoolThreadCache { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(PoolThreadCache.class); + + final PoolArena heapArena; + final PoolArena directArena; + + // Hold the caches for the different size classes, which are tiny, small and normal. + private final MemoryRegionCache[] tinySubPageHeapCaches; + private final MemoryRegionCache[] smallSubPageHeapCaches; + private final MemoryRegionCache[] tinySubPageDirectCaches; + private final MemoryRegionCache[] smallSubPageDirectCaches; + private final MemoryRegionCache[] normalHeapCaches; + private final MemoryRegionCache[] normalDirectCaches; + + // Used for bitshifting when calculate the index of normal caches later + private final int numShiftsNormalDirect; + private final int numShiftsNormalHeap; + private final int freeSweepAllocationThreshold; + + private int allocations; + + private final Thread thread = Thread.currentThread(); + private final Runnable freeTask = new Runnable() { + @Override + public void run() { + free0(); + } + }; + + // TODO: Test if adding padding helps under contention + //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + PoolThreadCache(PoolArena heapArena, PoolArena directArena, + int tinyCacheSize, int smallCacheSize, int normalCacheSize, + int maxCachedBufferCapacity, int freeSweepAllocationThreshold) { + if (maxCachedBufferCapacity < 0) { + throw new IllegalArgumentException("maxCachedBufferCapacity: " + + maxCachedBufferCapacity + " (expected: >= 0)"); + } + if (freeSweepAllocationThreshold < 1) { + throw new IllegalArgumentException("freeSweepAllocationThreshold: " + + maxCachedBufferCapacity + " (expected: > 0)"); + } + this.freeSweepAllocationThreshold = freeSweepAllocationThreshold; + this.heapArena = heapArena; + this.directArena = directArena; + if (directArena != null) { + tinySubPageDirectCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools); + smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.numSmallSubpagePools); + + numShiftsNormalDirect = log2(directArena.pageSize); + normalDirectCaches = createNormalCaches( + normalCacheSize, maxCachedBufferCapacity, directArena); + } else { + // No directArea is configured so just null out all caches + tinySubPageDirectCaches = null; + smallSubPageDirectCaches = null; + normalDirectCaches = null; + numShiftsNormalDirect = -1; + } + if (heapArena != null) { + // Create the caches for the heap allocations + tinySubPageHeapCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools); + smallSubPageHeapCaches = createSubPageCaches(smallCacheSize, heapArena.numSmallSubpagePools); + + numShiftsNormalHeap = log2(heapArena.pageSize); + normalHeapCaches = createNormalCaches( + normalCacheSize, maxCachedBufferCapacity, heapArena); + } else { + // No heapArea is configured so just null out all caches + tinySubPageHeapCaches = null; + smallSubPageHeapCaches = null; + normalHeapCaches = null; + numShiftsNormalHeap = -1; + } + + // The thread-local cache will keep a list of pooled buffers which must be returned to + // the pool when the thread is not alive anymore. + ThreadDeathWatcher.watch(thread, freeTask); + } + + private static SubPageMemoryRegionCache[] createSubPageCaches(int cacheSize, int numCaches) { + if (cacheSize > 0) { + + SubPageMemoryRegionCache[] cache = new SubPageMemoryRegionCache[numCaches]; + for (int i = 0; i < cache.length; i++) { + // TODO: maybe use cacheSize / cache.length + cache[i] = new SubPageMemoryRegionCache(cacheSize); + } + return cache; + } else { + return null; + } + } + + private static NormalMemoryRegionCache[] createNormalCaches( + int cacheSize, int maxCachedBufferCapacity, PoolArena area) { + if (cacheSize > 0) { + int max = Math.min(area.chunkSize, maxCachedBufferCapacity); + int arraySize = Math.max(1, max / area.pageSize); + + + NormalMemoryRegionCache[] cache = new NormalMemoryRegionCache[arraySize]; + for (int i = 0; i < cache.length; i++) { + cache[i] = new NormalMemoryRegionCache(cacheSize); + } + return cache; + } else { + return null; + } + } + + private static int log2(int val) { + int res = 0; + while (val > 1) { + val >>= 1; + res++; + } + return res; + } + + /** + * Try to allocate a tiny buffer out of the cache. Returns {@code true} if successful {@code false} otherwise + */ + boolean allocateTiny(PoolArena area, PooledByteBuf buf, int reqCapacity, int normCapacity) { + return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity); + } + + /** + * Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise + */ + boolean allocateSmall(PoolArena area, PooledByteBuf buf, int reqCapacity, int normCapacity) { + return allocate(cacheForSmall(area, normCapacity), buf, reqCapacity); + } + + /** + * Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise + */ + boolean allocateNormal(PoolArena area, PooledByteBuf buf, int reqCapacity, int normCapacity) { + return allocate(cacheForNormal(area, normCapacity), buf, reqCapacity); + } + + + private boolean allocate(MemoryRegionCache cache, PooledByteBuf buf, int reqCapacity) { + if (cache == null) { + // no cache found so just return false here + return false; + } + boolean allocated = cache.allocate(buf, reqCapacity); + if (++ allocations >= freeSweepAllocationThreshold) { + allocations = 0; + trim(); + } + return allocated; + } + + /** + * Add {@link PoolChunk} and {@code handle} to the cache if there is enough room. + * Returns {@code true} if it fit into the cache {@code false} otherwise. + */ + + boolean add(PoolArena area, PoolChunk chunk, long handle, int normCapacity) { + MemoryRegionCache cache; + if (area.isTinyOrSmall(normCapacity)) { + if (PoolArena.isTiny(normCapacity)) { + cache = cacheForTiny(area, normCapacity); + } else { + cache = cacheForSmall(area, normCapacity); + } + } else { + cache = cacheForNormal(area, normCapacity); + } + if (cache == null) { + return false; + } + return cache.add(chunk, handle); + } + + /** + * Should be called if the Thread that uses this cache is about to exist to release resources out of the cache + */ + void free() { + ThreadDeathWatcher.unwatch(thread, freeTask); + free0(); + } + + private void free0() { + int numFreed = free(tinySubPageDirectCaches) + + free(smallSubPageDirectCaches) + + free(normalDirectCaches) + + free(tinySubPageHeapCaches) + + free(smallSubPageHeapCaches) + + free(normalHeapCaches); + + if (numFreed > 0 && logger.isDebugEnabled()) { + logger.debug("Freed {} thread-local buffer(s) from thread: {}", numFreed, thread.getName()); + } + } + + private static int free(MemoryRegionCache[] caches) { + if (caches == null) { + return 0; + } + + int numFreed = 0; + for (MemoryRegionCache c: caches) { + numFreed += free(c); + } + return numFreed; + } + + private static int free(MemoryRegionCache cache) { + if (cache == null) { + return 0; + } + return cache.free(); + } + + void trim() { + trim(tinySubPageDirectCaches); + trim(smallSubPageDirectCaches); + trim(normalDirectCaches); + trim(tinySubPageHeapCaches); + trim(smallSubPageHeapCaches); + trim(normalHeapCaches); + } + + private static void trim(MemoryRegionCache[] caches) { + if (caches == null) { + return; + } + for (MemoryRegionCache c: caches) { + trim(c); + } + } + + private static void trim(MemoryRegionCache cache) { + if (cache == null) { + return; + } + cache.trim(); + } + + private MemoryRegionCache cacheForTiny(PoolArena area, int normCapacity) { + int idx = PoolArena.tinyIdx(normCapacity); + if (area.isDirect()) { + return cache(tinySubPageDirectCaches, idx); + } + return cache(tinySubPageHeapCaches, idx); + } + + private MemoryRegionCache cacheForSmall(PoolArena area, int normCapacity) { + int idx = PoolArena.smallIdx(normCapacity); + if (area.isDirect()) { + return cache(smallSubPageDirectCaches, idx); + } + return cache(smallSubPageHeapCaches, idx); + } + + private MemoryRegionCache cacheForNormal(PoolArena area, int normCapacity) { + if (area.isDirect()) { + int idx = log2(normCapacity >> numShiftsNormalDirect); + return cache(normalDirectCaches, idx); + } + int idx = log2(normCapacity >> numShiftsNormalHeap); + return cache(normalHeapCaches, idx); + } + + private static MemoryRegionCache cache(MemoryRegionCache[] cache, int idx) { + if (cache == null || idx > cache.length - 1) { + return null; + } + return cache[idx]; + } + + /** + * Cache used for buffers which are backed by TINY or SMALL size. + */ + private static final class SubPageMemoryRegionCache extends MemoryRegionCache { + SubPageMemoryRegionCache(int size) { + super(size); + } + + @Override + protected void initBuf( + PoolChunk chunk, long handle, PooledByteBuf buf, int reqCapacity) { + chunk.initBufWithSubpage(buf, handle, reqCapacity); + } + } + + /** + * Cache used for buffers which are backed by NORMAL size. + */ + private static final class NormalMemoryRegionCache extends MemoryRegionCache { + NormalMemoryRegionCache(int size) { + super(size); + } + + @Override + protected void initBuf( + PoolChunk chunk, long handle, PooledByteBuf buf, int reqCapacity) { + chunk.initBuf(buf, handle, reqCapacity); + } + } + + /** + * Cache of {@link PoolChunk} and handles which can be used to allocate a buffer without locking at all. + */ + private abstract static class MemoryRegionCache { + private final Entry[] entries; + private final int maxUnusedCached; + private int head; + private int tail; + private int maxEntriesInUse; + private int entriesInUse; + + + MemoryRegionCache(int size) { + entries = new Entry[powerOfTwo(size)]; + for (int i = 0; i < entries.length; i++) { + entries[i] = new Entry(); + } + maxUnusedCached = size / 2; + } + + private static int powerOfTwo(int res) { + if (res <= 2) { + return 2; + } + res--; + res |= res >> 1; + res |= res >> 2; + res |= res >> 4; + res |= res >> 8; + res |= res >> 16; + res++; + return res; + } + + /** + * Init the {@link PooledByteBuf} using the provided chunk and handle with the capacity restrictions. + */ + protected abstract void initBuf(PoolChunk chunk, long handle, + PooledByteBuf buf, int reqCapacity); + + /** + * Add to cache if not already full. + */ + public boolean add(PoolChunk chunk, long handle) { + Entry entry = entries[tail]; + if (entry.chunk != null) { + // cache is full + return false; + } + entriesInUse --; + + entry.chunk = chunk; + entry.handle = handle; + tail = nextIdx(tail); + return true; + } + + /** + * Allocate something out of the cache if possible and remove the entry from the cache. + */ + public boolean allocate(PooledByteBuf buf, int reqCapacity) { + Entry entry = entries[head]; + if (entry.chunk == null) { + return false; + } + + entriesInUse ++; + if (maxEntriesInUse < entriesInUse) { + maxEntriesInUse = entriesInUse; + } + initBuf(entry.chunk, entry.handle, buf, reqCapacity); + // only null out the chunk as we only use the chunk to check if the buffer is full or not. + entry.chunk = null; + head = nextIdx(head); + return true; + } + + /** + * Clear out this cache and free up all previous cached {@link PoolChunk}s and {@code handle}s. + */ + public int free() { + int numFreed = 0; + entriesInUse = 0; + maxEntriesInUse = 0; + for (int i = head;; i = nextIdx(i)) { + if (freeEntry(entries[i])) { + numFreed++; + } else { + // all cleared + return numFreed; + } + } + } + + /** + * Free up cached {@link PoolChunk}s if not allocated frequently enough. + */ + private void trim() { + int free = size() - maxEntriesInUse; + entriesInUse = 0; + maxEntriesInUse = 0; + + if (free <= maxUnusedCached) { + return; + } + + int i = head; + for (; free > 0; free--) { + if (!freeEntry(entries[i])) { + // all freed + return; + } + i = nextIdx(i); + } + } + + + private static boolean freeEntry(Entry entry) { + PoolChunk chunk = entry.chunk; + if (chunk == null) { + return false; + } + // need to synchronize on the area from which it was allocated before. + synchronized (chunk.arena) { + chunk.parent.free(chunk, entry.handle); + } + entry.chunk = null; + return true; + } + + /** + * Return the number of cached entries. + */ + private int size() { + return tail - head & entries.length - 1; + } + + private int nextIdx(int index) { + // use bitwise operation as this is faster as using modulo. + return index + 1 & entries.length - 1; + } + + private static final class Entry { + PoolChunk chunk; + long handle; + } + } +} diff --git a/common/src/common/net/buffer/PooledByteBuf.java b/common/src/common/net/buffer/PooledByteBuf.java new file mode 100644 index 0000000..34324d7 --- /dev/null +++ b/common/src/common/net/buffer/PooledByteBuf.java @@ -0,0 +1,160 @@ +/* + * 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 common.net.buffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import common.net.util.Recycler; + +abstract class PooledByteBuf extends AbstractReferenceCountedByteBuf { + + private final Recycler.Handle recyclerHandle; + + protected PoolChunk chunk; + protected long handle; + protected T memory; + protected int offset; + protected int length; + int maxLength; + + private ByteBuffer tmpNioBuf; + + protected PooledByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) { + super(maxCapacity); + this.recyclerHandle = recyclerHandle; + } + + void init(PoolChunk chunk, long handle, int offset, int length, int maxLength) { + assert handle >= 0; + assert chunk != null; + + this.chunk = chunk; + this.handle = handle; + memory = chunk.memory; + this.offset = offset; + this.length = length; + this.maxLength = maxLength; + setIndex(0, 0); + tmpNioBuf = null; + } + + void initUnpooled(PoolChunk chunk, int length) { + assert chunk != null; + + this.chunk = chunk; + handle = 0; + memory = chunk.memory; + offset = 0; + this.length = maxLength = length; + setIndex(0, 0); + tmpNioBuf = null; + } + + @Override + public final int capacity() { + return length; + } + + @Override + public final ByteBuf capacity(int newCapacity) { + ensureAccessible(); + + // If the request capacity does not require reallocation, just update the length of the memory. + if (chunk.unpooled) { + if (newCapacity == length) { + return this; + } + } else { + if (newCapacity > length) { + if (newCapacity <= maxLength) { + length = newCapacity; + return this; + } + } else if (newCapacity < length) { + if (newCapacity > maxLength >>> 1) { + if (maxLength <= 512) { + if (newCapacity > maxLength - 16) { + length = newCapacity; + setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity)); + return this; + } + } else { // > 512 (i.e. >= 1024) + length = newCapacity; + setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity)); + return this; + } + } + } else { + return this; + } + } + + // Reallocation required. + chunk.arena.reallocate(this, newCapacity, true); + return this; + } + + @Override + public final ByteBufAllocator alloc() { + return chunk.arena.parent; + } + + @Override + public final ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + @Override + public final ByteBuf unwrap() { + return null; + } + + protected final ByteBuffer internalNioBuffer() { + ByteBuffer tmpNioBuf = this.tmpNioBuf; + if (tmpNioBuf == null) { + this.tmpNioBuf = tmpNioBuf = newInternalNioBuffer(memory); + } + return tmpNioBuf; + } + + protected abstract ByteBuffer newInternalNioBuffer(T memory); + + @Override + protected final void deallocate() { + if (handle >= 0) { + final long handle = this.handle; + this.handle = -1; + memory = null; + chunk.arena.free(chunk, handle, maxLength); + recycle(); + } + } + + private void recycle() { + Recycler.Handle recyclerHandle = this.recyclerHandle; + if (recyclerHandle != null) { + ((Recycler) recycler()).recycle(this, recyclerHandle); + } + } + + protected abstract Recycler recycler(); + + protected final int idx(int index) { + return offset + index; + } +} diff --git a/common/src/common/net/buffer/PooledByteBufAllocator.java b/common/src/common/net/buffer/PooledByteBufAllocator.java new file mode 100644 index 0000000..4aaf1a0 --- /dev/null +++ b/common/src/common/net/buffer/PooledByteBufAllocator.java @@ -0,0 +1,334 @@ +/* + * 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 common.net.buffer; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; + +import common.net.util.concurrent.FastThreadLocal; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.SystemPropertyUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +public class PooledByteBufAllocator extends AbstractByteBufAllocator { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(PooledByteBufAllocator.class); + private static final int DEFAULT_NUM_HEAP_ARENA; + private static final int DEFAULT_NUM_DIRECT_ARENA; + + private static final int DEFAULT_PAGE_SIZE; + private static final int DEFAULT_MAX_ORDER; // 8192 << 11 = 16 MiB per chunk + private static final int DEFAULT_TINY_CACHE_SIZE; + private static final int DEFAULT_SMALL_CACHE_SIZE; + private static final int DEFAULT_NORMAL_CACHE_SIZE; + private static final int DEFAULT_MAX_CACHED_BUFFER_CAPACITY; + private static final int DEFAULT_CACHE_TRIM_INTERVAL; + + private static final int MIN_PAGE_SIZE = 4096; + private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2); + + static { + int defaultPageSize = SystemPropertyUtil.getInt("game.net.allocator.pageSize", 8192); + Throwable pageSizeFallbackCause = null; + try { + validateAndCalculatePageShifts(defaultPageSize); + } catch (Throwable t) { + pageSizeFallbackCause = t; + defaultPageSize = 8192; + } + DEFAULT_PAGE_SIZE = defaultPageSize; + + int defaultMaxOrder = SystemPropertyUtil.getInt("game.net.allocator.maxOrder", 11); + Throwable maxOrderFallbackCause = null; + try { + validateAndCalculateChunkSize(DEFAULT_PAGE_SIZE, defaultMaxOrder); + } catch (Throwable t) { + maxOrderFallbackCause = t; + defaultMaxOrder = 11; + } + DEFAULT_MAX_ORDER = defaultMaxOrder; + + // Determine reasonable default for nHeapArena and nDirectArena. + // Assuming each arena has 3 chunks, the pool should not consume more than 50% of max memory. + final Runtime runtime = Runtime.getRuntime(); + final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER; + DEFAULT_NUM_HEAP_ARENA = Math.max(0, + SystemPropertyUtil.getInt( + "game.net.allocator.numHeapArenas", + (int) Math.min( + runtime.availableProcessors(), + Runtime.getRuntime().maxMemory() / defaultChunkSize / 2 / 3))); + DEFAULT_NUM_DIRECT_ARENA = Math.max(0, + SystemPropertyUtil.getInt( + "game.net.allocator.numDirectArenas", + (int) Math.min( + runtime.availableProcessors(), + PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3))); + + // cache sizes + DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.tinyCacheSize", 512); + DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.smallCacheSize", 256); + DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.normalCacheSize", 64); + + // 32 kb is the default maximum capacity of the cached buffer. Similar to what is explained in + // 'Scalable memory allocation using jemalloc' + DEFAULT_MAX_CACHED_BUFFER_CAPACITY = SystemPropertyUtil.getInt( + "game.net.allocator.maxCachedBufferCapacity", 32 * 1024); + + // the number of threshold of allocations when cached entries will be freed up if not frequently used + DEFAULT_CACHE_TRIM_INTERVAL = SystemPropertyUtil.getInt( + "game.net.allocator.cacheTrimInterval", 8192); + + if (logger.isDebugEnabled()) { + logger.debug("-Dgame.net.allocator.numHeapArenas: {}", DEFAULT_NUM_HEAP_ARENA); + logger.debug("-Dgame.net.allocator.numDirectArenas: {}", DEFAULT_NUM_DIRECT_ARENA); + if (pageSizeFallbackCause == null) { + logger.debug("-Dgame.net.allocator.pageSize: {}", DEFAULT_PAGE_SIZE); + } else { + logger.debug("-Dgame.net.allocator.pageSize: {}", DEFAULT_PAGE_SIZE, pageSizeFallbackCause); + } + if (maxOrderFallbackCause == null) { + logger.debug("-Dgame.net.allocator.maxOrder: {}", DEFAULT_MAX_ORDER); + } else { + logger.debug("-Dgame.net.allocator.maxOrder: {}", DEFAULT_MAX_ORDER, maxOrderFallbackCause); + } + logger.debug("-Dgame.net.allocator.chunkSize: {}", DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER); + logger.debug("-Dgame.net.allocator.tinyCacheSize: {}", DEFAULT_TINY_CACHE_SIZE); + logger.debug("-Dgame.net.allocator.smallCacheSize: {}", DEFAULT_SMALL_CACHE_SIZE); + logger.debug("-Dgame.net.allocator.normalCacheSize: {}", DEFAULT_NORMAL_CACHE_SIZE); + logger.debug("-Dgame.net.allocator.maxCachedBufferCapacity: {}", DEFAULT_MAX_CACHED_BUFFER_CAPACITY); + logger.debug("-Dgame.net.allocator.cacheTrimInterval: {}", DEFAULT_CACHE_TRIM_INTERVAL); + } + } + + public static final PooledByteBufAllocator DEFAULT = + new PooledByteBufAllocator(PlatformDependent.directBufferPreferred()); + + private final PoolArena[] heapArenas; + private final PoolArena[] directArenas; + private final int tinyCacheSize; + private final int smallCacheSize; + private final int normalCacheSize; + + final PoolThreadLocalCache threadCache; + + public PooledByteBufAllocator() { + this(false); + } + + public PooledByteBufAllocator(boolean preferDirect) { + this(preferDirect, DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA, DEFAULT_PAGE_SIZE, DEFAULT_MAX_ORDER); + } + + public PooledByteBufAllocator(int nHeapArena, int nDirectArena, int pageSize, int maxOrder) { + this(false, nHeapArena, nDirectArena, pageSize, maxOrder); + } + + public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder) { + this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder, + DEFAULT_TINY_CACHE_SIZE, DEFAULT_SMALL_CACHE_SIZE, DEFAULT_NORMAL_CACHE_SIZE); + } + + public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder, + int tinyCacheSize, int smallCacheSize, int normalCacheSize) { + super(preferDirect); + threadCache = new PoolThreadLocalCache(); + this.tinyCacheSize = tinyCacheSize; + this.smallCacheSize = smallCacheSize; + this.normalCacheSize = normalCacheSize; + final int chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder); + + if (nHeapArena < 0) { + throw new IllegalArgumentException("nHeapArena: " + nHeapArena + " (expected: >= 0)"); + } + if (nDirectArena < 0) { + throw new IllegalArgumentException("nDirectArea: " + nDirectArena + " (expected: >= 0)"); + } + + int pageShifts = validateAndCalculatePageShifts(pageSize); + + if (nHeapArena > 0) { + heapArenas = newArenaArray(nHeapArena); + for (int i = 0; i < heapArenas.length; i ++) { + heapArenas[i] = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize); + } + } else { + heapArenas = null; + } + + if (nDirectArena > 0) { + directArenas = newArenaArray(nDirectArena); + for (int i = 0; i < directArenas.length; i ++) { + directArenas[i] = new PoolArena.DirectArena(this, pageSize, maxOrder, pageShifts, chunkSize); + } + } else { + directArenas = null; + } + } + + @Deprecated + + public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder, + int tinyCacheSize, int smallCacheSize, int normalCacheSize, + long cacheThreadAliveCheckInterval) { + this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder, + tinyCacheSize, smallCacheSize, normalCacheSize); + } + + + private static PoolArena[] newArenaArray(int size) { + return new PoolArena[size]; + } + + private static int validateAndCalculatePageShifts(int pageSize) { + if (pageSize < MIN_PAGE_SIZE) { + throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + "+)"); + } + + if ((pageSize & pageSize - 1) != 0) { + throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)"); + } + + // Logarithm base 2. At this point we know that pageSize is a power of two. + return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize); + } + + private static int validateAndCalculateChunkSize(int pageSize, int maxOrder) { + if (maxOrder > 14) { + throw new IllegalArgumentException("maxOrder: " + maxOrder + " (expected: 0-14)"); + } + + // Ensure the resulting chunkSize does not overflow. + int chunkSize = pageSize; + for (int i = maxOrder; i > 0; i --) { + if (chunkSize > MAX_CHUNK_SIZE / 2) { + throw new IllegalArgumentException(String.format( + "pageSize (%d) << maxOrder (%d) must not exceed %d", pageSize, maxOrder, MAX_CHUNK_SIZE)); + } + chunkSize <<= 1; + } + return chunkSize; + } + + @Override + protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) { + PoolThreadCache cache = threadCache.get(); + PoolArena heapArena = cache.heapArena; + + ByteBuf buf; + if (heapArena != null) { + buf = heapArena.allocate(cache, initialCapacity, maxCapacity); + } else { + buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity); + } + + return toLeakAwareBuffer(buf); + } + + @Override + protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { + PoolThreadCache cache = threadCache.get(); + PoolArena directArena = cache.directArena; + + ByteBuf buf; + if (directArena != null) { + buf = directArena.allocate(cache, initialCapacity, maxCapacity); + } else { + if (PlatformDependent.hasUnsafe()) { + buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity); + } else { + buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); + } + } + + return toLeakAwareBuffer(buf); + } + + @Override + public boolean isDirectBufferPooled() { + return directArenas != null; + } + + /** + * Returns {@code true} if the calling {@link Thread} has a {@link ThreadLocal} cache for the allocated + * buffers. + */ + @Deprecated + public boolean hasThreadLocalCache() { + return threadCache.isSet(); + } + + /** + * Free all cached buffers for the calling {@link Thread}. + */ + @Deprecated + public void freeThreadLocalCache() { + threadCache.remove(); + } + + final class PoolThreadLocalCache extends FastThreadLocal { + private final AtomicInteger index = new AtomicInteger(); + + @Override + protected PoolThreadCache initialValue() { + final int idx = index.getAndIncrement(); + final PoolArena heapArena; + final PoolArena directArena; + + if (heapArenas != null) { + heapArena = heapArenas[Math.abs(idx % heapArenas.length)]; + } else { + heapArena = null; + } + + if (directArenas != null) { + directArena = directArenas[Math.abs(idx % directArenas.length)]; + } else { + directArena = null; + } + + return new PoolThreadCache( + heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize, + DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL); + } + + @Override + protected void onRemoval(PoolThreadCache value) { + value.free(); + } + } + +// Too noisy at the moment. +// +// public String toString() { +// StringBuilder buf = new StringBuilder(); +// buf.append(heapArenas.length); +// buf.append(" heap arena(s):"); +// buf.append(StringUtil.NEWLINE); +// for (PoolArena a: heapArenas) { +// buf.append(a); +// } +// buf.append(directArenas.length); +// buf.append(" direct arena(s):"); +// buf.append(StringUtil.NEWLINE); +// for (PoolArena a: directArenas) { +// buf.append(a); +// } +// return buf.toString(); +// } +} diff --git a/common/src/common/net/buffer/PooledDirectByteBuf.java b/common/src/common/net/buffer/PooledDirectByteBuf.java new file mode 100644 index 0000000..74b7e55 --- /dev/null +++ b/common/src/common/net/buffer/PooledDirectByteBuf.java @@ -0,0 +1,377 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.Recycler; + +final class PooledDirectByteBuf extends PooledByteBuf { + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected PooledDirectByteBuf newObject(Handle handle) { + return new PooledDirectByteBuf(handle, 0); + } + }; + + static PooledDirectByteBuf newInstance(int maxCapacity) { + PooledDirectByteBuf buf = RECYCLER.get(); + buf.setRefCnt(1); + buf.maxCapacity(maxCapacity); + return buf; + } + + private PooledDirectByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) { + super(recyclerHandle, maxCapacity); + } + + @Override + protected ByteBuffer newInternalNioBuffer(ByteBuffer memory) { + return memory.duplicate(); + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + protected byte _getByte(int index) { + return memory.get(idx(index)); + } + + @Override + protected short _getShort(int index) { + return memory.getShort(idx(index)); + } + + @Override + protected int _getUnsignedMedium(int index) { + index = idx(index); + return (memory.get(index) & 0xff) << 16 | (memory.get(index + 1) & 0xff) << 8 | memory.get(index + 2) & 0xff; + } + + @Override + protected int _getInt(int index) { + return memory.getInt(idx(index)); + } + + @Override + protected long _getLong(int index) { + return memory.getLong(idx(index)); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (dst.hasArray()) { + getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length); + } else if (dst.nioBufferCount() > 0) { + for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) { + int bbLen = bb.remaining(); + getBytes(index, bb); + index += bbLen; + } + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + getBytes(index, dst, dstIndex, length, false); + return this; + } + + private void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) { + checkDstIndex(index, length, dstIndex, dst.length); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = memory.duplicate(); + } + index = idx(index); + tmpBuf.clear().position(index).limit(index + length); + tmpBuf.get(dst, dstIndex, length); + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + checkReadableBytes(length); + getBytes(readerIndex, dst, dstIndex, length, true); + readerIndex += length; + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + getBytes(index, dst, false); + return this; + } + + private void getBytes(int index, ByteBuffer dst, boolean internal) { + checkIndex(index); + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = memory.duplicate(); + } + index = idx(index); + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + int length = dst.remaining(); + checkReadableBytes(length); + getBytes(readerIndex, dst, true); + readerIndex += length; + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + getBytes(index, out, length, false); + return this; + } + + private void getBytes(int index, OutputStream out, int length, boolean internal) throws IOException { + checkIndex(index, length); + if (length == 0) { + return; + } + + byte[] tmp = new byte[length]; + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = memory.duplicate(); + } + tmpBuf.clear().position(idx(index)); + tmpBuf.get(tmp); + out.write(tmp); + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + checkReadableBytes(length); + getBytes(readerIndex, out, length, true); + readerIndex += length; + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return getBytes(index, out, length, false); + } + + private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException { + checkIndex(index, length); + if (length == 0) { + return 0; + } + + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = memory.duplicate(); + } + index = idx(index); + tmpBuf.clear().position(index).limit(index + length); + return out.write(tmpBuf); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length, true); + readerIndex += readBytes; + return readBytes; + } + + @Override + protected void _setByte(int index, int value) { + memory.put(idx(index), (byte) value); + } + + @Override + protected void _setShort(int index, int value) { + memory.putShort(idx(index), (short) value); + } + + @Override + protected void _setMedium(int index, int value) { + index = idx(index); + memory.put(index, (byte) (value >>> 16)); + memory.put(index + 1, (byte) (value >>> 8)); + memory.put(index + 2, (byte) value); + } + + @Override + protected void _setInt(int index, int value) { + memory.putInt(idx(index), value); + } + + @Override + protected void _setLong(int index, long value) { + memory.putLong(idx(index), value); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.capacity()); + if (src.hasArray()) { + setBytes(index, src.array(), src.arrayOffset() + srcIndex, length); + } else if (src.nioBufferCount() > 0) { + for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) { + int bbLen = bb.remaining(); + setBytes(index, bb); + index += bbLen; + } + } else { + src.getBytes(srcIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.length); + ByteBuffer tmpBuf = internalNioBuffer(); + index = idx(index); + tmpBuf.clear().position(index).limit(index + length); + tmpBuf.put(src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + checkIndex(index, src.remaining()); + ByteBuffer tmpBuf = internalNioBuffer(); + if (src == tmpBuf) { + src = src.duplicate(); + } + + index = idx(index); + tmpBuf.clear().position(index).limit(index + src.remaining()); + tmpBuf.put(src); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + checkIndex(index, length); + byte[] tmp = new byte[length]; + int readBytes = in.read(tmp); + if (readBytes <= 0) { + return readBytes; + } + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(idx(index)); + tmpBuf.put(tmp, 0, readBytes); + return readBytes; + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + checkIndex(index, length); + ByteBuffer tmpBuf = internalNioBuffer(); + index = idx(index); + tmpBuf.clear().position(index).limit(index + length); + try { + return in.read(tmpBuf); + } catch (ClosedChannelException ignored) { + return -1; + } + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + ByteBuf copy = alloc().directBuffer(length, maxCapacity()); + copy.writeBytes(this, index, length); + return copy; + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + index = idx(index); + return ((ByteBuffer) memory.duplicate().position(index).limit(index + length)).slice(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + index = idx(index); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public int arrayOffset() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } + + @Override + protected Recycler recycler() { + return RECYCLER; + } +} diff --git a/common/src/common/net/buffer/PooledHeapByteBuf.java b/common/src/common/net/buffer/PooledHeapByteBuf.java new file mode 100644 index 0000000..33e0ec6 --- /dev/null +++ b/common/src/common/net/buffer/PooledHeapByteBuf.java @@ -0,0 +1,307 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file tothe 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.Recycler; +import common.net.util.internal.PlatformDependent; + +final class PooledHeapByteBuf extends PooledByteBuf { + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected PooledHeapByteBuf newObject(Handle handle) { + return new PooledHeapByteBuf(handle, 0); + } + }; + + static PooledHeapByteBuf newInstance(int maxCapacity) { + PooledHeapByteBuf buf = RECYCLER.get(); + buf.setRefCnt(1); + buf.maxCapacity(maxCapacity); + return buf; + } + + private PooledHeapByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) { + super(recyclerHandle, maxCapacity); + } + + @Override + public boolean isDirect() { + return false; + } + + @Override + protected byte _getByte(int index) { + return memory[idx(index)]; + } + + @Override + protected short _getShort(int index) { + index = idx(index); + return (short) (memory[index] << 8 | memory[index + 1] & 0xFF); + } + + @Override + protected int _getUnsignedMedium(int index) { + index = idx(index); + return (memory[index] & 0xff) << 16 | + (memory[index + 1] & 0xff) << 8 | + memory[index + 2] & 0xff; + } + + @Override + protected int _getInt(int index) { + index = idx(index); + return (memory[index] & 0xff) << 24 | + (memory[index + 1] & 0xff) << 16 | + (memory[index + 2] & 0xff) << 8 | + memory[index + 3] & 0xff; + } + + @Override + protected long _getLong(int index) { + index = idx(index); + return ((long) memory[index] & 0xff) << 56 | + ((long) memory[index + 1] & 0xff) << 48 | + ((long) memory[index + 2] & 0xff) << 40 | + ((long) memory[index + 3] & 0xff) << 32 | + ((long) memory[index + 4] & 0xff) << 24 | + ((long) memory[index + 5] & 0xff) << 16 | + ((long) memory[index + 6] & 0xff) << 8 | + (long) memory[index + 7] & 0xff; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (dst.hasMemoryAddress()) { + PlatformDependent.copyMemory(memory, idx(index), dst.memoryAddress() + dstIndex, length); + } else if (dst.hasArray()) { + getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length); + } else { + dst.setBytes(dstIndex, memory, idx(index), length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.length); + System.arraycopy(memory, idx(index), dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + checkIndex(index); + dst.put(memory, idx(index), Math.min(capacity() - index, dst.remaining())); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + checkIndex(index, length); + out.write(memory, idx(index), length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return getBytes(index, out, length, false); + } + + private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException { + checkIndex(index, length); + index = idx(index); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = ByteBuffer.wrap(memory); + } + return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length)); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length, true); + readerIndex += readBytes; + return readBytes; + } + + @Override + protected void _setByte(int index, int value) { + memory[idx(index)] = (byte) value; + } + + @Override + protected void _setShort(int index, int value) { + index = idx(index); + memory[index] = (byte) (value >>> 8); + memory[index + 1] = (byte) value; + } + + @Override + protected void _setMedium(int index, int value) { + index = idx(index); + memory[index] = (byte) (value >>> 16); + memory[index + 1] = (byte) (value >>> 8); + memory[index + 2] = (byte) value; + } + + @Override + protected void _setInt(int index, int value) { + index = idx(index); + memory[index] = (byte) (value >>> 24); + memory[index + 1] = (byte) (value >>> 16); + memory[index + 2] = (byte) (value >>> 8); + memory[index + 3] = (byte) value; + } + + @Override + protected void _setLong(int index, long value) { + index = idx(index); + memory[index] = (byte) (value >>> 56); + memory[index + 1] = (byte) (value >>> 48); + memory[index + 2] = (byte) (value >>> 40); + memory[index + 3] = (byte) (value >>> 32); + memory[index + 4] = (byte) (value >>> 24); + memory[index + 5] = (byte) (value >>> 16); + memory[index + 6] = (byte) (value >>> 8); + memory[index + 7] = (byte) value; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.capacity()); + if (src.hasMemoryAddress()) { + PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, memory, idx(index), length); + } else if (src.hasArray()) { + setBytes(index, src.array(), src.arrayOffset() + srcIndex, length); + } else { + src.getBytes(srcIndex, memory, idx(index), length); + } + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.length); + System.arraycopy(src, srcIndex, memory, idx(index), length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + int length = src.remaining(); + checkIndex(index, length); + src.get(memory, idx(index), length); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + checkIndex(index, length); + return in.read(memory, idx(index), length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + checkIndex(index, length); + index = idx(index); + try { + return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length)); + } catch (ClosedChannelException ignored) { + return -1; + } + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + ByteBuf copy = alloc().heapBuffer(length, maxCapacity()); + copy.writeBytes(memory, idx(index), length); + return copy; + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + index = idx(index); + ByteBuffer buf = ByteBuffer.wrap(memory, index, length); + return buf.slice(); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + index = idx(index); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + @Override + public boolean hasArray() { + return true; + } + + @Override + public byte[] array() { + return memory; + } + + @Override + public int arrayOffset() { + return offset; + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } + + @Override + protected ByteBuffer newInternalNioBuffer(byte[] memory) { + return ByteBuffer.wrap(memory); + } + + @Override + protected Recycler recycler() { + return RECYCLER; + } +} diff --git a/common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java b/common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java new file mode 100644 index 0000000..e28103f --- /dev/null +++ b/common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java @@ -0,0 +1,394 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.Recycler; +import common.net.util.internal.PlatformDependent; + +final class PooledUnsafeDirectByteBuf extends PooledByteBuf { + + private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected PooledUnsafeDirectByteBuf newObject(Handle handle) { + return new PooledUnsafeDirectByteBuf(handle, 0); + } + }; + + static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) { + PooledUnsafeDirectByteBuf buf = RECYCLER.get(); + buf.setRefCnt(1); + buf.maxCapacity(maxCapacity); + return buf; + } + + private long memoryAddress; + + private PooledUnsafeDirectByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) { + super(recyclerHandle, maxCapacity); + } + + @Override + void init(PoolChunk chunk, long handle, int offset, int length, int maxLength) { + super.init(chunk, handle, offset, length, maxLength); + initMemoryAddress(); + } + + @Override + void initUnpooled(PoolChunk chunk, int length) { + super.initUnpooled(chunk, length); + initMemoryAddress(); + } + + private void initMemoryAddress() { + memoryAddress = PlatformDependent.directBufferAddress(memory) + offset; + } + + @Override + protected ByteBuffer newInternalNioBuffer(ByteBuffer memory) { + return memory.duplicate(); + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + protected byte _getByte(int index) { + return PlatformDependent.getByte(addr(index)); + } + + @Override + protected short _getShort(int index) { + short v = PlatformDependent.getShort(addr(index)); + return NATIVE_ORDER? v : Short.reverseBytes(v); + } + + @Override + protected int _getUnsignedMedium(int index) { + long addr = addr(index); + return (PlatformDependent.getByte(addr) & 0xff) << 16 | + (PlatformDependent.getByte(addr + 1) & 0xff) << 8 | + PlatformDependent.getByte(addr + 2) & 0xff; + } + + @Override + protected int _getInt(int index) { + int v = PlatformDependent.getInt(addr(index)); + return NATIVE_ORDER? v : Integer.reverseBytes(v); + } + + @Override + protected long _getLong(int index) { + long v = PlatformDependent.getLong(addr(index)); + return NATIVE_ORDER? v : Long.reverseBytes(v); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.capacity() - length) { + throw new IndexOutOfBoundsException("dstIndex: " + dstIndex); + } + + if (length != 0) { + if (dst.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length); + } else if (dst.hasArray()) { + PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length); + } else { + dst.setBytes(dstIndex, this, index, length); + } + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException("dstIndex: " + dstIndex); + } + if (length != 0) { + PlatformDependent.copyMemory(addr(index), dst, dstIndex, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + getBytes(index, dst, false); + return this; + } + + private void getBytes(int index, ByteBuffer dst, boolean internal) { + checkIndex(index); + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = memory.duplicate(); + } + index = idx(index); + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + int length = dst.remaining(); + checkReadableBytes(length); + getBytes(readerIndex, dst, true); + readerIndex += length; + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + checkIndex(index, length); + if (length != 0) { + byte[] tmp = new byte[length]; + PlatformDependent.copyMemory(addr(index), tmp, 0, length); + out.write(tmp); + } + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return getBytes(index, out, length, false); + } + + private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException { + checkIndex(index, length); + if (length == 0) { + return 0; + } + + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = memory.duplicate(); + } + index = idx(index); + tmpBuf.clear().position(index).limit(index + length); + return out.write(tmpBuf); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) + throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length, true); + readerIndex += readBytes; + return readBytes; + } + + @Override + protected void _setByte(int index, int value) { + PlatformDependent.putByte(addr(index), (byte) value); + } + + @Override + protected void _setShort(int index, int value) { + PlatformDependent.putShort(addr(index), NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value)); + } + + @Override + protected void _setMedium(int index, int value) { + long addr = addr(index); + PlatformDependent.putByte(addr, (byte) (value >>> 16)); + PlatformDependent.putByte(addr + 1, (byte) (value >>> 8)); + PlatformDependent.putByte(addr + 2, (byte) value); + } + + @Override + protected void _setInt(int index, int value) { + PlatformDependent.putInt(addr(index), NATIVE_ORDER ? value : Integer.reverseBytes(value)); + } + + @Override + protected void _setLong(int index, long value) { + PlatformDependent.putLong(addr(index), NATIVE_ORDER ? value : Long.reverseBytes(value)); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkIndex(index, length); + if (src == null) { + throw new NullPointerException("src"); + } + if (srcIndex < 0 || srcIndex > src.capacity() - length) { + throw new IndexOutOfBoundsException("srcIndex: " + srcIndex); + } + + if (length != 0) { + if (src.hasMemoryAddress()) { + PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr(index), length); + } else if (src.hasArray()) { + PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr(index), length); + } else { + src.getBytes(srcIndex, this, index, length); + } + } + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkIndex(index, length); + if (length != 0) { + PlatformDependent.copyMemory(src, srcIndex, addr(index), length); + } + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + checkIndex(index, src.remaining()); + ByteBuffer tmpBuf = internalNioBuffer(); + if (src == tmpBuf) { + src = src.duplicate(); + } + + index = idx(index); + tmpBuf.clear().position(index).limit(index + src.remaining()); + tmpBuf.put(src); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + checkIndex(index, length); + byte[] tmp = new byte[length]; + int readBytes = in.read(tmp); + if (readBytes > 0) { + PlatformDependent.copyMemory(tmp, 0, addr(index), readBytes); + } + return readBytes; + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + checkIndex(index, length); + ByteBuffer tmpBuf = internalNioBuffer(); + index = idx(index); + tmpBuf.clear().position(index).limit(index + length); + try { + return in.read(tmpBuf); + } catch (ClosedChannelException ignored) { + return -1; + } + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + ByteBuf copy = alloc().directBuffer(length, maxCapacity()); + if (length != 0) { + if (copy.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length); + copy.setIndex(0, length); + } else { + copy.writeBytes(this, index, length); + } + } + return copy; + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + index = idx(index); + return ((ByteBuffer) memory.duplicate().position(index).limit(index + length)).slice(); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + index = idx(index); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public int arrayOffset() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public boolean hasMemoryAddress() { + return true; + } + + @Override + public long memoryAddress() { + return memoryAddress; + } + + private long addr(int index) { + return memoryAddress + index; + } + + @Override + protected Recycler recycler() { + return RECYCLER; + } + + @Override + protected SwappedByteBuf newSwappedByteBuf() { + return new UnsafeDirectSwappedByteBuf(this); + } +} diff --git a/common/src/common/net/buffer/ReadOnlyByteBuf.java b/common/src/common/net/buffer/ReadOnlyByteBuf.java new file mode 100644 index 0000000..17a80ac --- /dev/null +++ b/common/src/common/net/buffer/ReadOnlyByteBuf.java @@ -0,0 +1,317 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +/** + * A derived buffer which forbids any write requests to its parent. It is + * recommended to use {@link Unpooled#unmodifiableBuffer(ByteBuf)} + * instead of calling the constructor explicitly. + */ +public class ReadOnlyByteBuf extends AbstractDerivedByteBuf { + + private final ByteBuf buffer; + + public ReadOnlyByteBuf(ByteBuf buffer) { + super(buffer.maxCapacity()); + + if (buffer instanceof ReadOnlyByteBuf || buffer instanceof DuplicatedByteBuf) { + this.buffer = buffer.unwrap(); + } else { + this.buffer = buffer; + } + setIndex(buffer.readerIndex(), buffer.writerIndex()); + } + + @Override + public boolean isWritable() { + return false; + } + + @Override + public boolean isWritable(int numBytes) { + return false; + } + + @Override + public ByteBuf unwrap() { + return buffer; + } + + @Override + public ByteBufAllocator alloc() { + return buffer.alloc(); + } + + @Override + public ByteOrder order() { + return buffer.order(); + } + + @Override + public boolean isDirect() { + return buffer.isDirect(); + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new ReadOnlyBufferException(); + } + + @Override + public int arrayOffset() { + throw new ReadOnlyBufferException(); + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf discardReadBytes() { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setByte(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setByte(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setShort(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setShort(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setMedium(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setMedium(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setInt(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setInt(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setLong(int index, long value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setLong(int index, long value) { + throw new ReadOnlyBufferException(); + } + + @Override + public int setBytes(int index, InputStream in, int length) { + throw new ReadOnlyBufferException(); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) { + throw new ReadOnlyBufferException(); + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) + throws IOException { + return buffer.getBytes(index, out, length); + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) + throws IOException { + buffer.getBytes(index, out, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + buffer.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + buffer.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + buffer.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf duplicate() { + return new ReadOnlyByteBuf(this); + } + + @Override + public ByteBuf copy(int index, int length) { + return buffer.copy(index, length); + } + + @Override + public ByteBuf slice(int index, int length) { + return Unpooled.unmodifiableBuffer(buffer.slice(index, length)); + } + + @Override + public byte getByte(int index) { + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + return buffer.getByte(index); + } + + @Override + public short getShort(int index) { + return _getShort(index); + } + + @Override + protected short _getShort(int index) { + return buffer.getShort(index); + } + + @Override + public int getUnsignedMedium(int index) { + return _getUnsignedMedium(index); + } + + @Override + protected int _getUnsignedMedium(int index) { + return buffer.getUnsignedMedium(index); + } + + @Override + public int getInt(int index) { + return _getInt(index); + } + + @Override + protected int _getInt(int index) { + return buffer.getInt(index); + } + + @Override + public long getLong(int index) { + return _getLong(index); + } + + @Override + protected long _getLong(int index) { + return buffer.getLong(index); + } + + @Override + public int nioBufferCount() { + return buffer.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return buffer.nioBuffer(index, length).asReadOnlyBuffer(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return buffer.nioBuffers(index, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return nioBuffer(index, length); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + return buffer.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + return buffer.forEachByteDesc(index, length, processor); + } + + @Override + public int capacity() { + return buffer.capacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + throw new ReadOnlyBufferException(); + } +} diff --git a/common/src/common/net/buffer/ReadOnlyByteBufferBuf.java b/common/src/common/net/buffer/ReadOnlyByteBufferBuf.java new file mode 100644 index 0000000..4d158b7 --- /dev/null +++ b/common/src/common/net/buffer/ReadOnlyByteBufferBuf.java @@ -0,0 +1,335 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.internal.StringUtil; + + +/** + * Read-only ByteBuf which wraps a read-only ByteBuffer. + */ +class ReadOnlyByteBufferBuf extends AbstractReferenceCountedByteBuf { + + protected final ByteBuffer buffer; + private final ByteBufAllocator allocator; + private ByteBuffer tmpNioBuf; + + ReadOnlyByteBufferBuf(ByteBufAllocator allocator, ByteBuffer buffer) { + super(buffer.remaining()); + if (!buffer.isReadOnly()) { + throw new IllegalArgumentException("must be a readonly buffer: " + StringUtil.simpleClassName(buffer)); + } + + this.allocator = allocator; + this.buffer = buffer.slice().order(ByteOrder.BIG_ENDIAN); + writerIndex(this.buffer.limit()); + } + + @Override + protected void deallocate() { } + + @Override + public byte getByte(int index) { + ensureAccessible(); + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + return buffer.get(index); + } + + @Override + public short getShort(int index) { + ensureAccessible(); + return _getShort(index); + } + + @Override + protected short _getShort(int index) { + return buffer.getShort(index); + } + + @Override + public int getUnsignedMedium(int index) { + ensureAccessible(); + return _getUnsignedMedium(index); + } + + @Override + protected int _getUnsignedMedium(int index) { + return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff; + } + + @Override + public int getInt(int index) { + ensureAccessible(); + return _getInt(index); + } + + @Override + protected int _getInt(int index) { + return buffer.getInt(index); + } + + @Override + public long getLong(int index) { + ensureAccessible(); + return _getLong(index); + } + + @Override + protected long _getLong(int index) { + return buffer.getLong(index); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (dst.hasArray()) { + getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length); + } else if (dst.nioBufferCount() > 0) { + for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) { + int bbLen = bb.remaining(); + getBytes(index, bb); + index += bbLen; + } + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.length); + + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length)); + } + + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + length); + tmpBuf.get(dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + checkIndex(index); + if (dst == null) { + throw new NullPointerException("dst"); + } + + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + return this; + } + + @Override + protected void _setByte(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setShort(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setMedium(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setInt(int index, int value) { + throw new ReadOnlyBufferException(); + } + + @Override + protected void _setLong(int index, long value) { + throw new ReadOnlyBufferException(); + } + + @Override + public int capacity() { + return maxCapacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBufAllocator alloc() { + return allocator; + } + + @Override + public ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + @Override + public ByteBuf unwrap() { + return null; + } + + @Override + public boolean isDirect() { + return buffer.isDirect(); + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + ensureAccessible(); + if (length == 0) { + return this; + } + + if (buffer.hasArray()) { + out.write(buffer.array(), index + buffer.arrayOffset(), length); + } else { + byte[] tmp = new byte[length]; + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index); + tmpBuf.get(tmp); + out.write(tmp); + } + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + ensureAccessible(); + if (length == 0) { + return 0; + } + + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + length); + return out.write(tmpBuf); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + throw new ReadOnlyBufferException(); + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + throw new ReadOnlyBufferException(); + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + throw new ReadOnlyBufferException(); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + throw new ReadOnlyBufferException(); + } + + protected final ByteBuffer internalNioBuffer() { + ByteBuffer tmpNioBuf = this.tmpNioBuf; + if (tmpNioBuf == null) { + this.tmpNioBuf = tmpNioBuf = buffer.duplicate(); + } + return tmpNioBuf; + } + + @Override + public ByteBuf copy(int index, int length) { + ensureAccessible(); + ByteBuffer src; + try { + src = (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } catch (IllegalArgumentException ignored) { + throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length)); + } + + ByteBuffer dst = ByteBuffer.allocateDirect(length); + dst.put(src); + dst.order(order()); + dst.clear(); + return new UnpooledDirectByteBuf(alloc(), dst, maxCapacity()); + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return (ByteBuffer) buffer.duplicate().position(index).limit(index + length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + ensureAccessible(); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + @Override + public boolean hasArray() { + return buffer.hasArray(); + } + + @Override + public byte[] array() { + return buffer.array(); + } + + @Override + public int arrayOffset() { + return buffer.arrayOffset(); + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } +} diff --git a/common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java b/common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java new file mode 100644 index 0000000..6b8981c --- /dev/null +++ b/common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java @@ -0,0 +1,138 @@ +/* + * Copyright 2013 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 common.net.buffer; + + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import common.net.util.internal.PlatformDependent; + + + +/** + * Read-only ByteBuf which wraps a read-only direct ByteBuffer and use unsafe for best performance. + */ +final class ReadOnlyUnsafeDirectByteBuf extends ReadOnlyByteBufferBuf { + private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + private final long memoryAddress; + + ReadOnlyUnsafeDirectByteBuf(ByteBufAllocator allocator, ByteBuffer buffer) { + super(allocator, buffer); + memoryAddress = PlatformDependent.directBufferAddress(buffer); + } + + @Override + protected byte _getByte(int index) { + return PlatformDependent.getByte(addr(index)); + } + + @Override + protected short _getShort(int index) { + short v = PlatformDependent.getShort(addr(index)); + return NATIVE_ORDER? v : Short.reverseBytes(v); + } + + @Override + protected int _getUnsignedMedium(int index) { + long addr = addr(index); + return (PlatformDependent.getByte(addr) & 0xff) << 16 | + (PlatformDependent.getByte(addr + 1) & 0xff) << 8 | + PlatformDependent.getByte(addr + 2) & 0xff; + } + + @Override + protected int _getInt(int index) { + int v = PlatformDependent.getInt(addr(index)); + return NATIVE_ORDER? v : Integer.reverseBytes(v); + } + + @Override + protected long _getLong(int index) { + long v = PlatformDependent.getLong(addr(index)); + return NATIVE_ORDER? v : Long.reverseBytes(v); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.capacity() - length) { + throw new IndexOutOfBoundsException("dstIndex: " + dstIndex); + } + + if (dst.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length); + } else if (dst.hasArray()) { + PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length); + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length)); + } + + if (length != 0) { + PlatformDependent.copyMemory(addr(index), dst, dstIndex, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + checkIndex(index); + if (dst == null) { + throw new NullPointerException("dst"); + } + + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + return this; + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + ByteBuf copy = alloc().directBuffer(length, maxCapacity()); + if (length != 0) { + if (copy.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length); + copy.setIndex(0, length); + } else { + copy.writeBytes(this, index, length); + } + } + return copy; + } + + private long addr(int index) { + return memoryAddress + index; + } +} diff --git a/common/src/common/net/buffer/SimpleLeakAwareByteBuf.java b/common/src/common/net/buffer/SimpleLeakAwareByteBuf.java new file mode 100644 index 0000000..688af60 --- /dev/null +++ b/common/src/common/net/buffer/SimpleLeakAwareByteBuf.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.nio.ByteOrder; + +import common.net.util.ResourceLeak; + +final class SimpleLeakAwareByteBuf extends WrappedByteBuf { + + private final ResourceLeak leak; + + SimpleLeakAwareByteBuf(ByteBuf buf, ResourceLeak leak) { + super(buf); + this.leak = leak; + } + + @Override + public boolean release() { + boolean deallocated = super.release(); + if (deallocated) { + leak.close(); + } + return deallocated; + } + + @Override + public boolean release(int decrement) { + boolean deallocated = super.release(decrement); + if (deallocated) { + leak.close(); + } + return deallocated; + } + + @Override + public ByteBuf order(ByteOrder endianness) { + leak.record(); + if (order() == endianness) { + return this; + } else { + return new SimpleLeakAwareByteBuf(super.order(endianness), leak); + } + } + + @Override + public ByteBuf slice() { + return new SimpleLeakAwareByteBuf(super.slice(), leak); + } + + @Override + public ByteBuf slice(int index, int length) { + return new SimpleLeakAwareByteBuf(super.slice(index, length), leak); + } + + @Override + public ByteBuf duplicate() { + return new SimpleLeakAwareByteBuf(super.duplicate(), leak); + } + + @Override + public ByteBuf readSlice(int length) { + return new SimpleLeakAwareByteBuf(super.readSlice(length), leak); + } +} diff --git a/common/src/common/net/buffer/SlicedByteBuf.java b/common/src/common/net/buffer/SlicedByteBuf.java new file mode 100644 index 0000000..0e7a208 --- /dev/null +++ b/common/src/common/net/buffer/SlicedByteBuf.java @@ -0,0 +1,296 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + + +/** + * A derived buffer which exposes its parent's sub-region only. It is + * recommended to use {@link ByteBuf#slice()} and + * {@link ByteBuf#slice(int, int)} instead of calling the constructor + * explicitly. + */ +public class SlicedByteBuf extends AbstractDerivedByteBuf { + + private final ByteBuf buffer; + private final int adjustment; + private final int length; + + public SlicedByteBuf(ByteBuf buffer, int index, int length) { + super(length); + if (index < 0 || index > buffer.capacity() - length) { + throw new IndexOutOfBoundsException(buffer + ".slice(" + index + ", " + length + ')'); + } + + if (buffer instanceof SlicedByteBuf) { + this.buffer = ((SlicedByteBuf) buffer).buffer; + adjustment = ((SlicedByteBuf) buffer).adjustment + index; + } else if (buffer instanceof DuplicatedByteBuf) { + this.buffer = buffer.unwrap(); + adjustment = index; + } else { + this.buffer = buffer; + adjustment = index; + } + this.length = length; + + writerIndex(length); + } + + @Override + public ByteBuf unwrap() { + return buffer; + } + + @Override + public ByteBufAllocator alloc() { + return buffer.alloc(); + } + + @Override + public ByteOrder order() { + return buffer.order(); + } + + @Override + public boolean isDirect() { + return buffer.isDirect(); + } + + @Override + public int capacity() { + return length; + } + + @Override + public ByteBuf capacity(int newCapacity) { + throw new UnsupportedOperationException("sliced buffer"); + } + + @Override + public boolean hasArray() { + return buffer.hasArray(); + } + + @Override + public byte[] array() { + return buffer.array(); + } + + @Override + public int arrayOffset() { + return buffer.arrayOffset() + adjustment; + } + + @Override + public boolean hasMemoryAddress() { + return buffer.hasMemoryAddress(); + } + + @Override + public long memoryAddress() { + return buffer.memoryAddress() + adjustment; + } + + @Override + protected byte _getByte(int index) { + return buffer.getByte(index + adjustment); + } + + @Override + protected short _getShort(int index) { + return buffer.getShort(index + adjustment); + } + + @Override + protected int _getUnsignedMedium(int index) { + return buffer.getUnsignedMedium(index + adjustment); + } + + @Override + protected int _getInt(int index) { + return buffer.getInt(index + adjustment); + } + + @Override + protected long _getLong(int index) { + return buffer.getLong(index + adjustment); + } + + @Override + public ByteBuf duplicate() { + ByteBuf duplicate = buffer.slice(adjustment, length); + duplicate.setIndex(readerIndex(), writerIndex()); + return duplicate; + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + return buffer.copy(index + adjustment, length); + } + + @Override + public ByteBuf slice(int index, int length) { + checkIndex(index, length); + if (length == 0) { + return Unpooled.EMPTY_BUFFER; + } + return buffer.slice(index + adjustment, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkIndex(index, length); + buffer.getBytes(index + adjustment, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkIndex(index, length); + buffer.getBytes(index + adjustment, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + checkIndex(index, dst.remaining()); + buffer.getBytes(index + adjustment, dst); + return this; + } + + @Override + protected void _setByte(int index, int value) { + buffer.setByte(index + adjustment, value); + } + + @Override + protected void _setShort(int index, int value) { + buffer.setShort(index + adjustment, value); + } + + @Override + protected void _setMedium(int index, int value) { + buffer.setMedium(index + adjustment, value); + } + + @Override + protected void _setInt(int index, int value) { + buffer.setInt(index + adjustment, value); + } + + @Override + protected void _setLong(int index, long value) { + buffer.setLong(index + adjustment, value); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkIndex(index, length); + buffer.setBytes(index + adjustment, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkIndex(index, length); + buffer.setBytes(index + adjustment, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + checkIndex(index, src.remaining()); + buffer.setBytes(index + adjustment, src); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + checkIndex(index, length); + buffer.getBytes(index + adjustment, out, length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + checkIndex(index, length); + return buffer.getBytes(index + adjustment, out, length); + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + checkIndex(index, length); + return buffer.setBytes(index + adjustment, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + checkIndex(index, length); + return buffer.setBytes(index + adjustment, in, length); + } + + @Override + public int nioBufferCount() { + return buffer.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + return buffer.nioBuffer(index + adjustment, length); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + checkIndex(index, length); + return buffer.nioBuffers(index + adjustment, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + return nioBuffer(index, length); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + int ret = buffer.forEachByte(index + adjustment, length, processor); + if (ret >= adjustment) { + return ret - adjustment; + } else { + return -1; + } + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + int ret = buffer.forEachByteDesc(index + adjustment, length, processor); + if (ret >= adjustment) { + return ret - adjustment; + } else { + return -1; + } + } +} diff --git a/common/src/common/net/buffer/SwappedByteBuf.java b/common/src/common/net/buffer/SwappedByteBuf.java new file mode 100644 index 0000000..88b4478 --- /dev/null +++ b/common/src/common/net/buffer/SwappedByteBuf.java @@ -0,0 +1,852 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; + +/** + * Wrapper which swap the {@link ByteOrder} of a {@link ByteBuf}. + */ +public class SwappedByteBuf extends ByteBuf { + + private final ByteBuf buf; + private final ByteOrder order; + + public SwappedByteBuf(ByteBuf buf) { + if (buf == null) { + throw new NullPointerException("buf"); + } + this.buf = buf; + if (buf.order() == ByteOrder.BIG_ENDIAN) { + order = ByteOrder.LITTLE_ENDIAN; + } else { + order = ByteOrder.BIG_ENDIAN; + } + } + + @Override + public ByteOrder order() { + return order; + } + + @Override + public ByteBuf order(ByteOrder endianness) { + if (endianness == null) { + throw new NullPointerException("endianness"); + } + if (endianness == order) { + return this; + } + return buf; + } + + @Override + public ByteBuf unwrap() { + return buf.unwrap(); + } + + @Override + public ByteBufAllocator alloc() { + return buf.alloc(); + } + + @Override + public int capacity() { + return buf.capacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + buf.capacity(newCapacity); + return this; + } + + @Override + public int maxCapacity() { + return buf.maxCapacity(); + } + + @Override + public boolean isDirect() { + return buf.isDirect(); + } + + @Override + public int readerIndex() { + return buf.readerIndex(); + } + + @Override + public ByteBuf readerIndex(int readerIndex) { + buf.readerIndex(readerIndex); + return this; + } + + @Override + public int writerIndex() { + return buf.writerIndex(); + } + + @Override + public ByteBuf writerIndex(int writerIndex) { + buf.writerIndex(writerIndex); + return this; + } + + @Override + public ByteBuf setIndex(int readerIndex, int writerIndex) { + buf.setIndex(readerIndex, writerIndex); + return this; + } + + @Override + public int readableBytes() { + return buf.readableBytes(); + } + + @Override + public int writableBytes() { + return buf.writableBytes(); + } + + @Override + public int maxWritableBytes() { + return buf.maxWritableBytes(); + } + + @Override + public boolean isReadable() { + return buf.isReadable(); + } + + @Override + public boolean isReadable(int size) { + return buf.isReadable(size); + } + + @Override + public boolean isWritable() { + return buf.isWritable(); + } + + @Override + public boolean isWritable(int size) { + return buf.isWritable(size); + } + + @Override + public ByteBuf clear() { + buf.clear(); + return this; + } + + @Override + public ByteBuf markReaderIndex() { + buf.markReaderIndex(); + return this; + } + + @Override + public ByteBuf resetReaderIndex() { + buf.resetReaderIndex(); + return this; + } + + @Override + public ByteBuf markWriterIndex() { + buf.markWriterIndex(); + return this; + } + + @Override + public ByteBuf resetWriterIndex() { + buf.resetWriterIndex(); + return this; + } + + @Override + public ByteBuf discardReadBytes() { + buf.discardReadBytes(); + return this; + } + + @Override + public ByteBuf discardSomeReadBytes() { + buf.discardSomeReadBytes(); + return this; + } + + @Override + public ByteBuf ensureWritable(int writableBytes) { + buf.ensureWritable(writableBytes); + return this; + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + return buf.ensureWritable(minWritableBytes, force); + } + + @Override + public boolean getBoolean(int index) { + return buf.getBoolean(index); + } + + @Override + public byte getByte(int index) { + return buf.getByte(index); + } + + @Override + public short getUnsignedByte(int index) { + return buf.getUnsignedByte(index); + } + + @Override + public short getShort(int index) { + return ByteBufUtil.swapShort(buf.getShort(index)); + } + + @Override + public int getUnsignedShort(int index) { + return getShort(index) & 0xFFFF; + } + + @Override + public int getMedium(int index) { + return ByteBufUtil.swapMedium(buf.getMedium(index)); + } + + @Override + public int getUnsignedMedium(int index) { + return getMedium(index) & 0xFFFFFF; + } + + @Override + public int getInt(int index) { + return ByteBufUtil.swapInt(buf.getInt(index)); + } + + @Override + public long getUnsignedInt(int index) { + return getInt(index) & 0xFFFFFFFFL; + } + + @Override + public long getLong(int index) { + return ByteBufUtil.swapLong(buf.getLong(index)); + } + + @Override + public char getChar(int index) { + return (char) getShort(index); + } + + @Override + public float getFloat(int index) { + return Float.intBitsToFloat(getInt(index)); + } + + @Override + public double getDouble(int index) { + return Double.longBitsToDouble(getLong(index)); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst) { + buf.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int length) { + buf.getBytes(index, dst, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + buf.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst) { + buf.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + buf.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + buf.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + buf.getBytes(index, out, length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return buf.getBytes(index, out, length); + } + + @Override + public ByteBuf setBoolean(int index, boolean value) { + buf.setBoolean(index, value); + return this; + } + + @Override + public ByteBuf setByte(int index, int value) { + buf.setByte(index, value); + return this; + } + + @Override + public ByteBuf setShort(int index, int value) { + buf.setShort(index, ByteBufUtil.swapShort((short) value)); + return this; + } + + @Override + public ByteBuf setMedium(int index, int value) { + buf.setMedium(index, ByteBufUtil.swapMedium(value)); + return this; + } + + @Override + public ByteBuf setInt(int index, int value) { + buf.setInt(index, ByteBufUtil.swapInt(value)); + return this; + } + + @Override + public ByteBuf setLong(int index, long value) { + buf.setLong(index, ByteBufUtil.swapLong(value)); + return this; + } + + @Override + public ByteBuf setChar(int index, int value) { + setShort(index, value); + return this; + } + + @Override + public ByteBuf setFloat(int index, float value) { + setInt(index, Float.floatToRawIntBits(value)); + return this; + } + + @Override + public ByteBuf setDouble(int index, double value) { + setLong(index, Double.doubleToRawLongBits(value)); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src) { + buf.setBytes(index, src); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int length) { + buf.setBytes(index, src, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + buf.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src) { + buf.setBytes(index, src); + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + buf.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + buf.setBytes(index, src); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + return buf.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + return buf.setBytes(index, in, length); + } + + @Override + public ByteBuf setZero(int index, int length) { + buf.setZero(index, length); + return this; + } + + @Override + public boolean readBoolean() { + return buf.readBoolean(); + } + + @Override + public byte readByte() { + return buf.readByte(); + } + + @Override + public short readUnsignedByte() { + return buf.readUnsignedByte(); + } + + @Override + public short readShort() { + return ByteBufUtil.swapShort(buf.readShort()); + } + + @Override + public int readUnsignedShort() { + return readShort() & 0xFFFF; + } + + @Override + public int readMedium() { + return ByteBufUtil.swapMedium(buf.readMedium()); + } + + @Override + public int readUnsignedMedium() { + return readMedium() & 0xFFFFFF; + } + + @Override + public int readInt() { + return ByteBufUtil.swapInt(buf.readInt()); + } + + @Override + public long readUnsignedInt() { + return readInt() & 0xFFFFFFFFL; + } + + @Override + public long readLong() { + return ByteBufUtil.swapLong(buf.readLong()); + } + + @Override + public char readChar() { + return (char) readShort(); + } + + @Override + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + @Override + public double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + @Override + public ByteBuf readBytes(int length) { + return buf.readBytes(length).order(order()); + } + + @Override + public ByteBuf readSlice(int length) { + return buf.readSlice(length).order(order); + } + + @Override + public ByteBuf readBytes(ByteBuf dst) { + buf.readBytes(dst); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int length) { + buf.readBytes(dst, length); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + buf.readBytes(dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf readBytes(byte[] dst) { + buf.readBytes(dst); + return this; + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + buf.readBytes(dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + buf.readBytes(dst); + return this; + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + buf.readBytes(out, length); + return this; + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + return buf.readBytes(out, length); + } + + @Override + public ByteBuf skipBytes(int length) { + buf.skipBytes(length); + return this; + } + + @Override + public ByteBuf writeBoolean(boolean value) { + buf.writeBoolean(value); + return this; + } + + @Override + public ByteBuf writeByte(int value) { + buf.writeByte(value); + return this; + } + + @Override + public ByteBuf writeShort(int value) { + buf.writeShort(ByteBufUtil.swapShort((short) value)); + return this; + } + + @Override + public ByteBuf writeMedium(int value) { + buf.writeMedium(ByteBufUtil.swapMedium(value)); + return this; + } + + @Override + public ByteBuf writeInt(int value) { + buf.writeInt(ByteBufUtil.swapInt(value)); + return this; + } + + @Override + public ByteBuf writeLong(long value) { + buf.writeLong(ByteBufUtil.swapLong(value)); + return this; + } + + @Override + public ByteBuf writeChar(int value) { + writeShort(value); + return this; + } + + @Override + public ByteBuf writeFloat(float value) { + writeInt(Float.floatToRawIntBits(value)); + return this; + } + + @Override + public ByteBuf writeDouble(double value) { + writeLong(Double.doubleToRawLongBits(value)); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src) { + buf.writeBytes(src); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int length) { + buf.writeBytes(src, length); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + buf.writeBytes(src, srcIndex, length); + return this; + } + + @Override + public ByteBuf writeBytes(byte[] src) { + buf.writeBytes(src); + return this; + } + + @Override + public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { + buf.writeBytes(src, srcIndex, length); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuffer src) { + buf.writeBytes(src); + return this; + } + + @Override + public int writeBytes(InputStream in, int length) throws IOException { + return buf.writeBytes(in, length); + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + return buf.writeBytes(in, length); + } + + @Override + public ByteBuf writeZero(int length) { + buf.writeZero(length); + return this; + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + return buf.indexOf(fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + return buf.bytesBefore(value); + } + + @Override + public int bytesBefore(int length, byte value) { + return buf.bytesBefore(length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + return buf.bytesBefore(index, length, value); + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + return buf.forEachByte(processor); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + return buf.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + return buf.forEachByteDesc(processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + return buf.forEachByteDesc(index, length, processor); + } + + @Override + public ByteBuf copy() { + return buf.copy().order(order); + } + + @Override + public ByteBuf copy(int index, int length) { + return buf.copy(index, length).order(order); + } + + @Override + public ByteBuf slice() { + return buf.slice().order(order); + } + + @Override + public ByteBuf slice(int index, int length) { + return buf.slice(index, length).order(order); + } + + @Override + public ByteBuf duplicate() { + return buf.duplicate().order(order); + } + + @Override + public int nioBufferCount() { + return buf.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer() { + return buf.nioBuffer().order(order); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return buf.nioBuffer(index, length).order(order); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return nioBuffer(index, length); + } + + @Override + public ByteBuffer[] nioBuffers() { + ByteBuffer[] nioBuffers = buf.nioBuffers(); + for (int i = 0; i < nioBuffers.length; i++) { + nioBuffers[i] = nioBuffers[i].order(order); + } + return nioBuffers; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + ByteBuffer[] nioBuffers = buf.nioBuffers(index, length); + for (int i = 0; i < nioBuffers.length; i++) { + nioBuffers[i] = nioBuffers[i].order(order); + } + return nioBuffers; + } + + @Override + public boolean hasArray() { + return buf.hasArray(); + } + + @Override + public byte[] array() { + return buf.array(); + } + + @Override + public int arrayOffset() { + return buf.arrayOffset(); + } + + @Override + public boolean hasMemoryAddress() { + return buf.hasMemoryAddress(); + } + + @Override + public long memoryAddress() { + return buf.memoryAddress(); + } + + @Override + public String toString(Charset charset) { + return buf.toString(charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + return buf.toString(index, length, charset); + } + + @Override + public int refCnt() { + return buf.refCnt(); + } + + @Override + public ByteBuf retain() { + buf.retain(); + return this; + } + + @Override + public ByteBuf retain(int increment) { + buf.retain(increment); + return this; + } + + @Override + public boolean release() { + return buf.release(); + } + + @Override + public boolean release(int decrement) { + return buf.release(decrement); + } + + @Override + public int hashCode() { + return buf.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ByteBuf) { + return ByteBufUtil.equals(this, (ByteBuf) obj); + } + return false; + } + + @Override + public int compareTo(ByteBuf buffer) { + return ByteBufUtil.compare(this, buffer); + } + + @Override + public String toString() { + return "Swapped(" + buf.toString() + ')'; + } +} diff --git a/common/src/common/net/buffer/Unpooled.java b/common/src/common/net/buffer/Unpooled.java new file mode 100644 index 0000000..8bb2d67 --- /dev/null +++ b/common/src/common/net/buffer/Unpooled.java @@ -0,0 +1,861 @@ +/* + * 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 common.net.buffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import common.net.util.internal.PlatformDependent; + + +/** + * Creates a new {@link ByteBuf} by allocating new space or by wrapping + * or copying existing byte arrays, byte buffers and a string. + * + *

Use static import

+ * This classes is intended to be used with Java 5 static import statement: + * + *
+ * import static game.net.buffer.{@link Unpooled}.*;
+ *
+ * {@link ByteBuf} heapBuffer    = buffer(128);
+ * {@link ByteBuf} directBuffer  = directBuffer(256);
+ * {@link ByteBuf} wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
+ * {@link ByteBuf} copiedBuffe r = copiedBuffer({@link ByteBuffer}.allocate(128));
+ * 
+ * + *

Allocating a new buffer

+ * + * Three buffer types are provided out of the box. + * + *
    + *
  • {@link #buffer(int)} allocates a new fixed-capacity heap buffer.
  • + *
  • {@link #directBuffer(int)} allocates a new fixed-capacity direct buffer.
  • + *
+ * + *

Creating a wrapped buffer

+ * + * Wrapped buffer is a buffer which is a view of one or more existing + * byte arrays and byte buffers. Any changes in the content of the original + * array or buffer will be visible in the wrapped buffer. Various wrapper + * methods are provided and their name is all {@code wrappedBuffer()}. + * You might want to take a look at the methods that accept varargs closely if + * you want to create a buffer which is composed of more than one array to + * reduce the number of memory copy. + * + *

Creating a copied buffer

+ * + * Copied buffer is a deep copy of one or more existing byte arrays, byte + * buffers or a string. Unlike a wrapped buffer, there's no shared data + * between the original data and the copied buffer. Various copy methods are + * provided and their name is all {@code copiedBuffer()}. It is also convenient + * to use this operation to merge multiple buffers into one buffer. + * + *

Miscellaneous utility methods

+ * + * This class also provides various utility methods to help implementation + * of a new buffer type, generation of hex dump and swapping an integer's + * byte order. + */ +public final class Unpooled { + + private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT; + + /** + * Big endian byte order. + */ + public static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN; + + /** + * Little endian byte order. + */ + public static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN; + + /** + * A buffer whose capacity is {@code 0}. + */ + public static final ByteBuf EMPTY_BUFFER = ALLOC.buffer(0, 0); + + /** + * Creates a new big-endian Java heap buffer with reasonably small initial capacity, which + * expands its capacity boundlessly on demand. + */ + public static ByteBuf buffer() { + return ALLOC.heapBuffer(); + } + + /** + * Creates a new big-endian direct buffer with reasonably small initial capacity, which + * expands its capacity boundlessly on demand. + */ + public static ByteBuf directBuffer() { + return ALLOC.directBuffer(); + } + + /** + * Creates a new big-endian Java heap buffer with the specified {@code capacity}, which + * expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and + * {@code writerIndex} are {@code 0}. + */ + public static ByteBuf buffer(int initialCapacity) { + return ALLOC.heapBuffer(initialCapacity); + } + + /** + * Creates a new big-endian direct buffer with the specified {@code capacity}, which + * expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and + * {@code writerIndex} are {@code 0}. + */ + public static ByteBuf directBuffer(int initialCapacity) { + return ALLOC.directBuffer(initialCapacity); + } + + /** + * Creates a new big-endian Java heap buffer with the specified + * {@code initialCapacity}, that may grow up to {@code maxCapacity} + * The new buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0}. + */ + public static ByteBuf buffer(int initialCapacity, int maxCapacity) { + return ALLOC.heapBuffer(initialCapacity, maxCapacity); + } + + /** + * Creates a new big-endian direct buffer with the specified + * {@code initialCapacity}, that may grow up to {@code maxCapacity}. + * The new buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0}. + */ + public static ByteBuf directBuffer(int initialCapacity, int maxCapacity) { + return ALLOC.directBuffer(initialCapacity, maxCapacity); + } + + /** + * Creates a new big-endian buffer which wraps the specified {@code array}. + * A modification on the specified array's content will be visible to the + * returned buffer. + */ + public static ByteBuf wrappedBuffer(byte[] array) { + if (array.length == 0) { + return EMPTY_BUFFER; + } + return new UnpooledHeapByteBuf(ALLOC, array, array.length); + } + + /** + * Creates a new big-endian buffer which wraps the sub-region of the + * specified {@code array}. A modification on the specified array's + * content will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(byte[] array, int offset, int length) { + if (length == 0) { + return EMPTY_BUFFER; + } + + if (offset == 0 && length == array.length) { + return wrappedBuffer(array); + } + + return wrappedBuffer(array).slice(offset, length); + } + + /** + * Creates a new buffer which wraps the specified NIO buffer's current + * slice. A modification on the specified buffer's content will be + * visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(ByteBuffer buffer) { + if (!buffer.hasRemaining()) { + return EMPTY_BUFFER; + } + if (buffer.hasArray()) { + return wrappedBuffer( + buffer.array(), + buffer.arrayOffset() + buffer.position(), + buffer.remaining()).order(buffer.order()); + } else if (PlatformDependent.hasUnsafe()) { + if (buffer.isReadOnly()) { + if (buffer.isDirect()) { + return new ReadOnlyUnsafeDirectByteBuf(ALLOC, buffer); + } else { + return new ReadOnlyByteBufferBuf(ALLOC, buffer); + } + } else { + return new UnpooledUnsafeDirectByteBuf(ALLOC, buffer, buffer.remaining()); + } + } else { + if (buffer.isReadOnly()) { + return new ReadOnlyByteBufferBuf(ALLOC, buffer); + } else { + return new UnpooledDirectByteBuf(ALLOC, buffer, buffer.remaining()); + } + } + } + + /** + * Creates a new buffer which wraps the specified buffer's readable bytes. + * A modification on the specified buffer's content will be visible to the + * returned buffer. + */ + public static ByteBuf wrappedBuffer(ByteBuf buffer) { + if (buffer.isReadable()) { + return buffer.slice(); + } else { + return EMPTY_BUFFER; + } + } + + /** + * Creates a new big-endian composite buffer which wraps the specified + * arrays without copying them. A modification on the specified arrays' + * content will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(byte[]... arrays) { + return wrappedBuffer(16, arrays); + } + + /** + * Creates a new big-endian composite buffer which wraps the readable bytes of the + * specified buffers without copying them. A modification on the content + * of the specified buffers will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(ByteBuf... buffers) { + return wrappedBuffer(16, buffers); + } + + /** + * Creates a new big-endian composite buffer which wraps the slices of the specified + * NIO buffers without copying them. A modification on the content of the + * specified buffers will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(ByteBuffer... buffers) { + return wrappedBuffer(16, buffers); + } + + /** + * Creates a new big-endian composite buffer which wraps the specified + * arrays without copying them. A modification on the specified arrays' + * content will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays) { + switch (arrays.length) { + case 0: + break; + case 1: + if (arrays[0].length != 0) { + return wrappedBuffer(arrays[0]); + } + break; + default: + // Get the list of the component, while guessing the byte order. + final List components = new ArrayList(arrays.length); + for (byte[] a: arrays) { + if (a == null) { + break; + } + if (a.length > 0) { + components.add(wrappedBuffer(a)); + } + } + + if (!components.isEmpty()) { + return new CompositeByteBuf(ALLOC, false, maxNumComponents, components); + } + } + + return EMPTY_BUFFER; + } + + /** + * Creates a new big-endian composite buffer which wraps the readable bytes of the + * specified buffers without copying them. A modification on the content + * of the specified buffers will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) { + switch (buffers.length) { + case 0: + break; + case 1: + if (buffers[0].isReadable()) { + return wrappedBuffer(buffers[0].order(BIG_ENDIAN)); + } + break; + default: + for (ByteBuf b: buffers) { + if (b.isReadable()) { + return new CompositeByteBuf(ALLOC, false, maxNumComponents, buffers); + } + } + } + return EMPTY_BUFFER; + } + + /** + * Creates a new big-endian composite buffer which wraps the slices of the specified + * NIO buffers without copying them. A modification on the content of the + * specified buffers will be visible to the returned buffer. + */ + public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers) { + switch (buffers.length) { + case 0: + break; + case 1: + if (buffers[0].hasRemaining()) { + return wrappedBuffer(buffers[0].order(BIG_ENDIAN)); + } + break; + default: + // Get the list of the component, while guessing the byte order. + final List components = new ArrayList(buffers.length); + for (ByteBuffer b: buffers) { + if (b == null) { + break; + } + if (b.remaining() > 0) { + components.add(wrappedBuffer(b.order(BIG_ENDIAN))); + } + } + + if (!components.isEmpty()) { + return new CompositeByteBuf(ALLOC, false, maxNumComponents, components); + } + } + + return EMPTY_BUFFER; + } + + /** + * Returns a new big-endian composite buffer with no components. + */ + public static CompositeByteBuf compositeBuffer() { + return compositeBuffer(16); + } + + /** + * Returns a new big-endian composite buffer with no components. + */ + public static CompositeByteBuf compositeBuffer(int maxNumComponents) { + return new CompositeByteBuf(ALLOC, false, maxNumComponents); + } + + /** + * Creates a new big-endian buffer whose content is a copy of the + * specified {@code array}. The new buffer's {@code readerIndex} and + * {@code writerIndex} are {@code 0} and {@code array.length} respectively. + */ + public static ByteBuf copiedBuffer(byte[] array) { + if (array.length == 0) { + return EMPTY_BUFFER; + } + return wrappedBuffer(array.clone()); + } + + /** + * Creates a new big-endian buffer whose content is a copy of the + * specified {@code array}'s sub-region. The new buffer's + * {@code readerIndex} and {@code writerIndex} are {@code 0} and + * the specified {@code length} respectively. + */ + public static ByteBuf copiedBuffer(byte[] array, int offset, int length) { + if (length == 0) { + return EMPTY_BUFFER; + } + byte[] copy = new byte[length]; + System.arraycopy(array, offset, copy, 0, length); + return wrappedBuffer(copy); + } + + /** + * Creates a new buffer whose content is a copy of the specified + * {@code buffer}'s current slice. The new buffer's {@code readerIndex} + * and {@code writerIndex} are {@code 0} and {@code buffer.remaining} + * respectively. + */ + public static ByteBuf copiedBuffer(ByteBuffer buffer) { + int length = buffer.remaining(); + if (length == 0) { + return EMPTY_BUFFER; + } + byte[] copy = new byte[length]; + int position = buffer.position(); + try { + buffer.get(copy); + } finally { + buffer.position(position); + } + return wrappedBuffer(copy).order(buffer.order()); + } + + /** + * Creates a new buffer whose content is a copy of the specified + * {@code buffer}'s readable bytes. The new buffer's {@code readerIndex} + * and {@code writerIndex} are {@code 0} and {@code buffer.readableBytes} + * respectively. + */ + public static ByteBuf copiedBuffer(ByteBuf buffer) { + int readable = buffer.readableBytes(); + if (readable > 0) { + ByteBuf copy = buffer(readable); + copy.writeBytes(buffer, buffer.readerIndex(), readable); + return copy; + } else { + return EMPTY_BUFFER; + } + } + + /** + * Creates a new big-endian buffer whose content is a merged copy of + * the specified {@code arrays}. The new buffer's {@code readerIndex} + * and {@code writerIndex} are {@code 0} and the sum of all arrays' + * {@code length} respectively. + */ + public static ByteBuf copiedBuffer(byte[]... arrays) { + switch (arrays.length) { + case 0: + return EMPTY_BUFFER; + case 1: + if (arrays[0].length == 0) { + return EMPTY_BUFFER; + } else { + return copiedBuffer(arrays[0]); + } + } + + // Merge the specified arrays into one array. + int length = 0; + for (byte[] a: arrays) { + if (Integer.MAX_VALUE - length < a.length) { + throw new IllegalArgumentException( + "The total length of the specified arrays is too big."); + } + length += a.length; + } + + if (length == 0) { + return EMPTY_BUFFER; + } + + byte[] mergedArray = new byte[length]; + for (int i = 0, j = 0; i < arrays.length; i ++) { + byte[] a = arrays[i]; + System.arraycopy(a, 0, mergedArray, j, a.length); + j += a.length; + } + + return wrappedBuffer(mergedArray); + } + + /** + * Creates a new buffer whose content is a merged copy of the specified + * {@code buffers}' readable bytes. The new buffer's {@code readerIndex} + * and {@code writerIndex} are {@code 0} and the sum of all buffers' + * {@code readableBytes} respectively. + * + * @throws IllegalArgumentException + * if the specified buffers' endianness are different from each + * other + */ + public static ByteBuf copiedBuffer(ByteBuf... buffers) { + switch (buffers.length) { + case 0: + return EMPTY_BUFFER; + case 1: + return copiedBuffer(buffers[0]); + } + + // Merge the specified buffers into one buffer. + ByteOrder order = null; + int length = 0; + for (ByteBuf b: buffers) { + int bLen = b.readableBytes(); + if (bLen <= 0) { + continue; + } + if (Integer.MAX_VALUE - length < bLen) { + throw new IllegalArgumentException( + "The total length of the specified buffers is too big."); + } + length += bLen; + if (order != null) { + if (!order.equals(b.order())) { + throw new IllegalArgumentException("inconsistent byte order"); + } + } else { + order = b.order(); + } + } + + if (length == 0) { + return EMPTY_BUFFER; + } + + byte[] mergedArray = new byte[length]; + for (int i = 0, j = 0; i < buffers.length; i ++) { + ByteBuf b = buffers[i]; + int bLen = b.readableBytes(); + b.getBytes(b.readerIndex(), mergedArray, j, bLen); + j += bLen; + } + + return wrappedBuffer(mergedArray).order(order); + } + + /** + * Creates a new buffer whose content is a merged copy of the specified + * {@code buffers}' slices. The new buffer's {@code readerIndex} and + * {@code writerIndex} are {@code 0} and the sum of all buffers' + * {@code remaining} respectively. + * + * @throws IllegalArgumentException + * if the specified buffers' endianness are different from each + * other + */ + public static ByteBuf copiedBuffer(ByteBuffer... buffers) { + switch (buffers.length) { + case 0: + return EMPTY_BUFFER; + case 1: + return copiedBuffer(buffers[0]); + } + + // Merge the specified buffers into one buffer. + ByteOrder order = null; + int length = 0; + for (ByteBuffer b: buffers) { + int bLen = b.remaining(); + if (bLen <= 0) { + continue; + } + if (Integer.MAX_VALUE - length < bLen) { + throw new IllegalArgumentException( + "The total length of the specified buffers is too big."); + } + length += bLen; + if (order != null) { + if (!order.equals(b.order())) { + throw new IllegalArgumentException("inconsistent byte order"); + } + } else { + order = b.order(); + } + } + + if (length == 0) { + return EMPTY_BUFFER; + } + + byte[] mergedArray = new byte[length]; + for (int i = 0, j = 0; i < buffers.length; i ++) { + ByteBuffer b = buffers[i]; + int bLen = b.remaining(); + int oldPos = b.position(); + b.get(mergedArray, j, bLen); + b.position(oldPos); + j += bLen; + } + + return wrappedBuffer(mergedArray).order(order); + } + + /** + * Creates a new big-endian buffer whose content is the specified + * {@code string} encoded in the specified {@code charset}. + * The new buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0} and the length of the encoded string respectively. + */ + public static ByteBuf copiedBuffer(CharSequence string, Charset charset) { + if (string == null) { + throw new NullPointerException("string"); + } + + if (string instanceof CharBuffer) { + return copiedBuffer((CharBuffer) string, charset); + } + + return copiedBuffer(CharBuffer.wrap(string), charset); + } + + /** + * Creates a new big-endian buffer whose content is a subregion of + * the specified {@code string} encoded in the specified {@code charset}. + * The new buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0} and the length of the encoded string respectively. + */ + public static ByteBuf copiedBuffer( + CharSequence string, int offset, int length, Charset charset) { + if (string == null) { + throw new NullPointerException("string"); + } + if (length == 0) { + return EMPTY_BUFFER; + } + + if (string instanceof CharBuffer) { + CharBuffer buf = (CharBuffer) string; + if (buf.hasArray()) { + return copiedBuffer( + buf.array(), + buf.arrayOffset() + buf.position() + offset, + length, charset); + } + + buf = buf.slice(); + buf.limit(length); + buf.position(offset); + return copiedBuffer(buf, charset); + } + + return copiedBuffer(CharBuffer.wrap(string, offset, offset + length), charset); + } + + /** + * Creates a new big-endian buffer whose content is the specified + * {@code array} encoded in the specified {@code charset}. + * The new buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0} and the length of the encoded string respectively. + */ + public static ByteBuf copiedBuffer(char[] array, Charset charset) { + if (array == null) { + throw new NullPointerException("array"); + } + return copiedBuffer(array, 0, array.length, charset); + } + + /** + * Creates a new big-endian buffer whose content is a subregion of + * the specified {@code array} encoded in the specified {@code charset}. + * The new buffer's {@code readerIndex} and {@code writerIndex} are + * {@code 0} and the length of the encoded string respectively. + */ + public static ByteBuf copiedBuffer(char[] array, int offset, int length, Charset charset) { + if (array == null) { + throw new NullPointerException("array"); + } + if (length == 0) { + return EMPTY_BUFFER; + } + return copiedBuffer(CharBuffer.wrap(array, offset, length), charset); + } + + private static ByteBuf copiedBuffer(CharBuffer buffer, Charset charset) { + return ByteBufUtil.encodeString0(ALLOC, true, buffer, charset); + } + + /** + * Creates a read-only buffer which disallows any modification operations + * on the specified {@code buffer}. The new buffer has the same + * {@code readerIndex} and {@code writerIndex} with the specified + * {@code buffer}. + */ + public static ByteBuf unmodifiableBuffer(ByteBuf buffer) { + ByteOrder endianness = buffer.order(); + if (endianness == BIG_ENDIAN) { + return new ReadOnlyByteBuf(buffer); + } + + return new ReadOnlyByteBuf(buffer.order(BIG_ENDIAN)).order(LITTLE_ENDIAN); + } + + /** + * Creates a new 4-byte big-endian buffer that holds the specified 32-bit integer. + */ + public static ByteBuf copyInt(int value) { + ByteBuf buf = buffer(4); + buf.writeInt(value); + return buf; + } + + /** + * Create a big-endian buffer that holds a sequence of the specified 32-bit integers. + */ + public static ByteBuf copyInt(int... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 4); + for (int v: values) { + buffer.writeInt(v); + } + return buffer; + } + + /** + * Creates a new 2-byte big-endian buffer that holds the specified 16-bit integer. + */ + public static ByteBuf copyShort(int value) { + ByteBuf buf = buffer(2); + buf.writeShort(value); + return buf; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified 16-bit integers. + */ + public static ByteBuf copyShort(short... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 2); + for (int v: values) { + buffer.writeShort(v); + } + return buffer; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified 16-bit integers. + */ + public static ByteBuf copyShort(int... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 2); + for (int v: values) { + buffer.writeShort(v); + } + return buffer; + } + + /** + * Creates a new 3-byte big-endian buffer that holds the specified 24-bit integer. + */ + public static ByteBuf copyMedium(int value) { + ByteBuf buf = buffer(3); + buf.writeMedium(value); + return buf; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified 24-bit integers. + */ + public static ByteBuf copyMedium(int... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 3); + for (int v: values) { + buffer.writeMedium(v); + } + return buffer; + } + + /** + * Creates a new 8-byte big-endian buffer that holds the specified 64-bit integer. + */ + public static ByteBuf copyLong(long value) { + ByteBuf buf = buffer(8); + buf.writeLong(value); + return buf; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified 64-bit integers. + */ + public static ByteBuf copyLong(long... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 8); + for (long v: values) { + buffer.writeLong(v); + } + return buffer; + } + + /** + * Creates a new single-byte big-endian buffer that holds the specified boolean value. + */ + public static ByteBuf copyBoolean(boolean value) { + ByteBuf buf = buffer(1); + buf.writeBoolean(value); + return buf; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified boolean values. + */ + public static ByteBuf copyBoolean(boolean... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length); + for (boolean v: values) { + buffer.writeBoolean(v); + } + return buffer; + } + + /** + * Creates a new 4-byte big-endian buffer that holds the specified 32-bit floating point number. + */ + public static ByteBuf copyFloat(float value) { + ByteBuf buf = buffer(4); + buf.writeFloat(value); + return buf; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified 32-bit floating point numbers. + */ + public static ByteBuf copyFloat(float... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 4); + for (float v: values) { + buffer.writeFloat(v); + } + return buffer; + } + + /** + * Creates a new 8-byte big-endian buffer that holds the specified 64-bit floating point number. + */ + public static ByteBuf copyDouble(double value) { + ByteBuf buf = buffer(8); + buf.writeDouble(value); + return buf; + } + + /** + * Create a new big-endian buffer that holds a sequence of the specified 64-bit floating point numbers. + */ + public static ByteBuf copyDouble(double... values) { + if (values == null || values.length == 0) { + return EMPTY_BUFFER; + } + ByteBuf buffer = buffer(values.length * 8); + for (double v: values) { + buffer.writeDouble(v); + } + return buffer; + } + + /** + * Return a unreleasable view on the given {@link ByteBuf} which will just ignore release and retain calls. + */ + public static ByteBuf unreleasableBuffer(ByteBuf buf) { + return new UnreleasableByteBuf(buf); + } + + private Unpooled() { + // Unused + } +} diff --git a/common/src/common/net/buffer/UnpooledByteBufAllocator.java b/common/src/common/net/buffer/UnpooledByteBufAllocator.java new file mode 100644 index 0000000..8b6183d --- /dev/null +++ b/common/src/common/net/buffer/UnpooledByteBufAllocator.java @@ -0,0 +1,62 @@ +/* + * 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 common.net.buffer; + +import common.net.util.internal.PlatformDependent; + +/** + * Simplistic {@link ByteBufAllocator} implementation that does not pool anything. + */ +public final class UnpooledByteBufAllocator extends AbstractByteBufAllocator { + + /** + * Default instance + */ + public static final UnpooledByteBufAllocator DEFAULT = + new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred()); + + /** + * Create a new instance + * + * @param preferDirect {@code true} if {@link #buffer(int)} should try to allocate a direct buffer rather than + * a heap buffer + */ + public UnpooledByteBufAllocator(boolean preferDirect) { + super(preferDirect); + } + + @Override + protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) { + return new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity); + } + + @Override + protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { + ByteBuf buf; + if (PlatformDependent.hasUnsafe()) { + buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity); + } else { + buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); + } + + return toLeakAwareBuffer(buf); + } + + @Override + public boolean isDirectBufferPooled() { + return false; + } +} diff --git a/common/src/common/net/buffer/UnpooledDirectByteBuf.java b/common/src/common/net/buffer/UnpooledDirectByteBuf.java new file mode 100644 index 0000000..76a738a --- /dev/null +++ b/common/src/common/net/buffer/UnpooledDirectByteBuf.java @@ -0,0 +1,604 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.internal.PlatformDependent; + +/** + * A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)} + * and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the + * constructor explicitly. + */ +public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf { + + private final ByteBufAllocator alloc; + + private ByteBuffer buffer; + private ByteBuffer tmpNioBuf; + private int capacity; + private boolean doNotFree; + + /** + * Creates a new direct buffer. + * + * @param initialCapacity the initial capacity of the underlying direct buffer + * @param maxCapacity the maximum capacity of the underlying direct buffer + */ + protected UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { + super(maxCapacity); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (initialCapacity < 0) { + throw new IllegalArgumentException("initialCapacity: " + initialCapacity); + } + if (maxCapacity < 0) { + throw new IllegalArgumentException("maxCapacity: " + maxCapacity); + } + if (initialCapacity > maxCapacity) { + throw new IllegalArgumentException(String.format( + "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity)); + } + + this.alloc = alloc; + setByteBuffer(ByteBuffer.allocateDirect(initialCapacity)); + } + + /** + * Creates a new direct buffer by wrapping the specified initial buffer. + * + * @param maxCapacity the maximum capacity of the underlying direct buffer + */ + protected UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) { + super(maxCapacity); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (initialBuffer == null) { + throw new NullPointerException("initialBuffer"); + } + if (!initialBuffer.isDirect()) { + throw new IllegalArgumentException("initialBuffer is not a direct buffer."); + } + if (initialBuffer.isReadOnly()) { + throw new IllegalArgumentException("initialBuffer is a read-only buffer."); + } + + int initialCapacity = initialBuffer.remaining(); + if (initialCapacity > maxCapacity) { + throw new IllegalArgumentException(String.format( + "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity)); + } + + this.alloc = alloc; + doNotFree = true; + setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN)); + writerIndex(initialCapacity); + } + + /** + * Allocate a new direct {@link ByteBuffer} with the given initialCapacity. + */ + protected ByteBuffer allocateDirect(int initialCapacity) { + return ByteBuffer.allocateDirect(initialCapacity); + } + + /** + * Free a direct {@link ByteBuffer} + */ + protected void freeDirect(ByteBuffer buffer) { + PlatformDependent.freeDirectBuffer(buffer); + } + + private void setByteBuffer(ByteBuffer buffer) { + ByteBuffer oldBuffer = this.buffer; + if (oldBuffer != null) { + if (doNotFree) { + doNotFree = false; + } else { + freeDirect(oldBuffer); + } + } + + this.buffer = buffer; + tmpNioBuf = null; + capacity = buffer.remaining(); + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public int capacity() { + return capacity; + } + + @Override + public ByteBuf capacity(int newCapacity) { + ensureAccessible(); + if (newCapacity < 0 || newCapacity > maxCapacity()) { + throw new IllegalArgumentException("newCapacity: " + newCapacity); + } + + int readerIndex = readerIndex(); + int writerIndex = writerIndex(); + + int oldCapacity = capacity; + if (newCapacity > oldCapacity) { + ByteBuffer oldBuffer = buffer; + ByteBuffer newBuffer = allocateDirect(newCapacity); + oldBuffer.position(0).limit(oldBuffer.capacity()); + newBuffer.position(0).limit(oldBuffer.capacity()); + newBuffer.put(oldBuffer); + newBuffer.clear(); + setByteBuffer(newBuffer); + } else if (newCapacity < oldCapacity) { + ByteBuffer oldBuffer = buffer; + ByteBuffer newBuffer = allocateDirect(newCapacity); + if (readerIndex < newCapacity) { + if (writerIndex > newCapacity) { + writerIndex(writerIndex = newCapacity); + } + oldBuffer.position(readerIndex).limit(writerIndex); + newBuffer.position(readerIndex).limit(writerIndex); + newBuffer.put(oldBuffer); + newBuffer.clear(); + } else { + setIndex(newCapacity, newCapacity); + } + setByteBuffer(newBuffer); + } + return this; + } + + @Override + public ByteBufAllocator alloc() { + return alloc; + } + + @Override + public ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public int arrayOffset() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } + + @Override + public byte getByte(int index) { + ensureAccessible(); + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + return buffer.get(index); + } + + @Override + public short getShort(int index) { + ensureAccessible(); + return _getShort(index); + } + + @Override + protected short _getShort(int index) { + return buffer.getShort(index); + } + + @Override + public int getUnsignedMedium(int index) { + ensureAccessible(); + return _getUnsignedMedium(index); + } + + @Override + protected int _getUnsignedMedium(int index) { + return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff; + } + + @Override + public int getInt(int index) { + ensureAccessible(); + return _getInt(index); + } + + @Override + protected int _getInt(int index) { + return buffer.getInt(index); + } + + @Override + public long getLong(int index) { + ensureAccessible(); + return _getLong(index); + } + + @Override + protected long _getLong(int index) { + return buffer.getLong(index); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (dst.hasArray()) { + getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length); + } else if (dst.nioBufferCount() > 0) { + for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) { + int bbLen = bb.remaining(); + getBytes(index, bb); + index += bbLen; + } + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + getBytes(index, dst, dstIndex, length, false); + return this; + } + + private void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) { + checkDstIndex(index, length, dstIndex, dst.length); + + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length)); + } + + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = buffer.duplicate(); + } + tmpBuf.clear().position(index).limit(index + length); + tmpBuf.get(dst, dstIndex, length); + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + checkReadableBytes(length); + getBytes(readerIndex, dst, dstIndex, length, true); + readerIndex += length; + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + getBytes(index, dst, false); + return this; + } + + private void getBytes(int index, ByteBuffer dst, boolean internal) { + checkIndex(index); + if (dst == null) { + throw new NullPointerException("dst"); + } + + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = buffer.duplicate(); + } + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + int length = dst.remaining(); + checkReadableBytes(length); + getBytes(readerIndex, dst, true); + readerIndex += length; + return this; + } + + @Override + public ByteBuf setByte(int index, int value) { + ensureAccessible(); + _setByte(index, value); + return this; + } + + @Override + protected void _setByte(int index, int value) { + buffer.put(index, (byte) value); + } + + @Override + public ByteBuf setShort(int index, int value) { + ensureAccessible(); + _setShort(index, value); + return this; + } + + @Override + protected void _setShort(int index, int value) { + buffer.putShort(index, (short) value); + } + + @Override + public ByteBuf setMedium(int index, int value) { + ensureAccessible(); + _setMedium(index, value); + return this; + } + + @Override + protected void _setMedium(int index, int value) { + setByte(index, (byte) (value >>> 16)); + setByte(index + 1, (byte) (value >>> 8)); + setByte(index + 2, (byte) value); + } + + @Override + public ByteBuf setInt(int index, int value) { + ensureAccessible(); + _setInt(index, value); + return this; + } + + @Override + protected void _setInt(int index, int value) { + buffer.putInt(index, value); + } + + @Override + public ByteBuf setLong(int index, long value) { + ensureAccessible(); + _setLong(index, value); + return this; + } + + @Override + protected void _setLong(int index, long value) { + buffer.putLong(index, value); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.capacity()); + if (src.nioBufferCount() > 0) { + for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) { + int bbLen = bb.remaining(); + setBytes(index, bb); + index += bbLen; + } + } else { + src.getBytes(srcIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.length); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + length); + tmpBuf.put(src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + ensureAccessible(); + ByteBuffer tmpBuf = internalNioBuffer(); + if (src == tmpBuf) { + src = src.duplicate(); + } + + tmpBuf.clear().position(index).limit(index + src.remaining()); + tmpBuf.put(src); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + getBytes(index, out, length, false); + return this; + } + + private void getBytes(int index, OutputStream out, int length, boolean internal) throws IOException { + ensureAccessible(); + if (length == 0) { + return; + } + + if (buffer.hasArray()) { + out.write(buffer.array(), index + buffer.arrayOffset(), length); + } else { + byte[] tmp = new byte[length]; + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = buffer.duplicate(); + } + tmpBuf.clear().position(index); + tmpBuf.get(tmp); + out.write(tmp); + } + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + checkReadableBytes(length); + getBytes(readerIndex, out, length, true); + readerIndex += length; + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return getBytes(index, out, length, false); + } + + private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException { + ensureAccessible(); + if (length == 0) { + return 0; + } + + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = buffer.duplicate(); + } + tmpBuf.clear().position(index).limit(index + length); + return out.write(tmpBuf); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length, true); + readerIndex += readBytes; + return readBytes; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + ensureAccessible(); + if (buffer.hasArray()) { + return in.read(buffer.array(), buffer.arrayOffset() + index, length); + } else { + byte[] tmp = new byte[length]; + int readBytes = in.read(tmp); + if (readBytes <= 0) { + return readBytes; + } + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index); + tmpBuf.put(tmp, 0, readBytes); + return readBytes; + } + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + ensureAccessible(); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + length); + try { + return in.read(tmpNioBuf); + } catch (ClosedChannelException ignored) { + return -1; + } + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuf copy(int index, int length) { + ensureAccessible(); + ByteBuffer src; + try { + src = (ByteBuffer) buffer.duplicate().clear().position(index).limit(index + length); + } catch (IllegalArgumentException ignored) { + throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length)); + } + + return alloc().directBuffer(length, maxCapacity()).writeBytes(src); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + private ByteBuffer internalNioBuffer() { + ByteBuffer tmpNioBuf = this.tmpNioBuf; + if (tmpNioBuf == null) { + this.tmpNioBuf = tmpNioBuf = buffer.duplicate(); + } + return tmpNioBuf; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + return ((ByteBuffer) buffer.duplicate().position(index).limit(index + length)).slice(); + } + + @Override + protected void deallocate() { + ByteBuffer buffer = this.buffer; + if (buffer == null) { + return; + } + + this.buffer = null; + + if (!doNotFree) { + freeDirect(buffer); + } + } + + @Override + public ByteBuf unwrap() { + return null; + } +} diff --git a/common/src/common/net/buffer/UnpooledHeapByteBuf.java b/common/src/common/net/buffer/UnpooledHeapByteBuf.java new file mode 100644 index 0000000..e56951f --- /dev/null +++ b/common/src/common/net/buffer/UnpooledHeapByteBuf.java @@ -0,0 +1,449 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.internal.PlatformDependent; + +/** + * Big endian Java heap buffer implementation. + */ +public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf { + + private final ByteBufAllocator alloc; + private byte[] array; + private ByteBuffer tmpNioBuf; + + /** + * Creates a new heap buffer with a newly allocated byte array. + * + * @param initialCapacity the initial capacity of the underlying byte array + * @param maxCapacity the max capacity of the underlying byte array + */ + protected UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { + this(alloc, new byte[initialCapacity], 0, 0, maxCapacity); + } + + /** + * Creates a new heap buffer with an existing byte array. + * + * @param initialArray the initial underlying byte array + * @param maxCapacity the max capacity of the underlying byte array + */ + protected UnpooledHeapByteBuf(ByteBufAllocator alloc, byte[] initialArray, int maxCapacity) { + this(alloc, initialArray, 0, initialArray.length, maxCapacity); + } + + private UnpooledHeapByteBuf( + ByteBufAllocator alloc, byte[] initialArray, int readerIndex, int writerIndex, int maxCapacity) { + + super(maxCapacity); + + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (initialArray == null) { + throw new NullPointerException("initialArray"); + } + if (initialArray.length > maxCapacity) { + throw new IllegalArgumentException(String.format( + "initialCapacity(%d) > maxCapacity(%d)", initialArray.length, maxCapacity)); + } + + this.alloc = alloc; + setArray(initialArray); + setIndex(readerIndex, writerIndex); + } + + private void setArray(byte[] initialArray) { + array = initialArray; + tmpNioBuf = null; + } + + @Override + public ByteBufAllocator alloc() { + return alloc; + } + + @Override + public ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + @Override + public boolean isDirect() { + return false; + } + + @Override + public int capacity() { + ensureAccessible(); + return array.length; + } + + @Override + public ByteBuf capacity(int newCapacity) { + ensureAccessible(); + if (newCapacity < 0 || newCapacity > maxCapacity()) { + throw new IllegalArgumentException("newCapacity: " + newCapacity); + } + + int oldCapacity = array.length; + if (newCapacity > oldCapacity) { + byte[] newArray = new byte[newCapacity]; + System.arraycopy(array, 0, newArray, 0, array.length); + setArray(newArray); + } else if (newCapacity < oldCapacity) { + byte[] newArray = new byte[newCapacity]; + int readerIndex = readerIndex(); + if (readerIndex < newCapacity) { + int writerIndex = writerIndex(); + if (writerIndex > newCapacity) { + writerIndex(writerIndex = newCapacity); + } + System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex); + } else { + setIndex(newCapacity, newCapacity); + } + setArray(newArray); + } + return this; + } + + @Override + public boolean hasArray() { + return true; + } + + @Override + public byte[] array() { + ensureAccessible(); + return array; + } + + @Override + public int arrayOffset() { + return 0; + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (dst.hasMemoryAddress()) { + PlatformDependent.copyMemory(array, index, dst.memoryAddress() + dstIndex, length); + } else if (dst.hasArray()) { + getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length); + } else { + dst.setBytes(dstIndex, array, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.length); + System.arraycopy(array, index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + ensureAccessible(); + dst.put(array, index, Math.min(capacity() - index, dst.remaining())); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + ensureAccessible(); + out.write(array, index, length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + ensureAccessible(); + return getBytes(index, out, length, false); + } + + private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException { + ensureAccessible(); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = ByteBuffer.wrap(array); + } + return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length)); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length, true); + readerIndex += readBytes; + return readBytes; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.capacity()); + if (src.hasMemoryAddress()) { + PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, array, index, length); + } else if (src.hasArray()) { + setBytes(index, src.array(), src.arrayOffset() + srcIndex, length); + } else { + src.getBytes(srcIndex, array, index, length); + } + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkSrcIndex(index, length, srcIndex, src.length); + System.arraycopy(src, srcIndex, array, index, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + ensureAccessible(); + src.get(array, index, src.remaining()); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + ensureAccessible(); + return in.read(array, index, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + ensureAccessible(); + try { + return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length)); + } catch (ClosedChannelException ignored) { + return -1; + } + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + ensureAccessible(); + return ByteBuffer.wrap(array, index, length).slice(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + @Override + public byte getByte(int index) { + ensureAccessible(); + return _getByte(index); + } + + @Override + protected byte _getByte(int index) { + return array[index]; + } + + @Override + public short getShort(int index) { + ensureAccessible(); + return _getShort(index); + } + + @Override + protected short _getShort(int index) { + return (short) (array[index] << 8 | array[index + 1] & 0xFF); + } + + @Override + public int getUnsignedMedium(int index) { + ensureAccessible(); + return _getUnsignedMedium(index); + } + + @Override + protected int _getUnsignedMedium(int index) { + return (array[index] & 0xff) << 16 | + (array[index + 1] & 0xff) << 8 | + array[index + 2] & 0xff; + } + + @Override + public int getInt(int index) { + ensureAccessible(); + return _getInt(index); + } + + @Override + protected int _getInt(int index) { + return (array[index] & 0xff) << 24 | + (array[index + 1] & 0xff) << 16 | + (array[index + 2] & 0xff) << 8 | + array[index + 3] & 0xff; + } + + @Override + public long getLong(int index) { + ensureAccessible(); + return _getLong(index); + } + + @Override + protected long _getLong(int index) { + return ((long) array[index] & 0xff) << 56 | + ((long) array[index + 1] & 0xff) << 48 | + ((long) array[index + 2] & 0xff) << 40 | + ((long) array[index + 3] & 0xff) << 32 | + ((long) array[index + 4] & 0xff) << 24 | + ((long) array[index + 5] & 0xff) << 16 | + ((long) array[index + 6] & 0xff) << 8 | + (long) array[index + 7] & 0xff; + } + + @Override + public ByteBuf setByte(int index, int value) { + ensureAccessible(); + _setByte(index, value); + return this; + } + + @Override + protected void _setByte(int index, int value) { + array[index] = (byte) value; + } + + @Override + public ByteBuf setShort(int index, int value) { + ensureAccessible(); + _setShort(index, value); + return this; + } + + @Override + protected void _setShort(int index, int value) { + array[index] = (byte) (value >>> 8); + array[index + 1] = (byte) value; + } + + @Override + public ByteBuf setMedium(int index, int value) { + ensureAccessible(); + _setMedium(index, value); + return this; + } + + @Override + protected void _setMedium(int index, int value) { + array[index] = (byte) (value >>> 16); + array[index + 1] = (byte) (value >>> 8); + array[index + 2] = (byte) value; + } + + @Override + public ByteBuf setInt(int index, int value) { + ensureAccessible(); + _setInt(index, value); + return this; + } + + @Override + protected void _setInt(int index, int value) { + array[index] = (byte) (value >>> 24); + array[index + 1] = (byte) (value >>> 16); + array[index + 2] = (byte) (value >>> 8); + array[index + 3] = (byte) value; + } + + @Override + public ByteBuf setLong(int index, long value) { + ensureAccessible(); + _setLong(index, value); + return this; + } + + @Override + protected void _setLong(int index, long value) { + array[index] = (byte) (value >>> 56); + array[index + 1] = (byte) (value >>> 48); + array[index + 2] = (byte) (value >>> 40); + array[index + 3] = (byte) (value >>> 32); + array[index + 4] = (byte) (value >>> 24); + array[index + 5] = (byte) (value >>> 16); + array[index + 6] = (byte) (value >>> 8); + array[index + 7] = (byte) value; + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + byte[] copiedArray = new byte[length]; + System.arraycopy(array, index, copiedArray, 0, length); + return new UnpooledHeapByteBuf(alloc(), copiedArray, maxCapacity()); + } + + private ByteBuffer internalNioBuffer() { + ByteBuffer tmpNioBuf = this.tmpNioBuf; + if (tmpNioBuf == null) { + this.tmpNioBuf = tmpNioBuf = ByteBuffer.wrap(array); + } + return tmpNioBuf; + } + + @Override + protected void deallocate() { + array = null; + } + + @Override + public ByteBuf unwrap() { + return null; + } +} diff --git a/common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java b/common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java new file mode 100644 index 0000000..a098a1a --- /dev/null +++ b/common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java @@ -0,0 +1,524 @@ +/* + * 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; + +import common.net.util.internal.PlatformDependent; + +/** + * A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)} + * and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the + * constructor explicitly. + */ +public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf { + + private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + + private final ByteBufAllocator alloc; + + private long memoryAddress; + private ByteBuffer buffer; + private ByteBuffer tmpNioBuf; + private int capacity; + private boolean doNotFree; + + /** + * Creates a new direct buffer. + * + * @param initialCapacity the initial capacity of the underlying direct buffer + * @param maxCapacity the maximum capacity of the underlying direct buffer + */ + protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { + super(maxCapacity); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (initialCapacity < 0) { + throw new IllegalArgumentException("initialCapacity: " + initialCapacity); + } + if (maxCapacity < 0) { + throw new IllegalArgumentException("maxCapacity: " + maxCapacity); + } + if (initialCapacity > maxCapacity) { + throw new IllegalArgumentException(String.format( + "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity)); + } + + this.alloc = alloc; + setByteBuffer(allocateDirect(initialCapacity)); + } + + /** + * Creates a new direct buffer by wrapping the specified initial buffer. + * + * @param maxCapacity the maximum capacity of the underlying direct buffer + */ + protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) { + super(maxCapacity); + if (alloc == null) { + throw new NullPointerException("alloc"); + } + if (initialBuffer == null) { + throw new NullPointerException("initialBuffer"); + } + if (!initialBuffer.isDirect()) { + throw new IllegalArgumentException("initialBuffer is not a direct buffer."); + } + if (initialBuffer.isReadOnly()) { + throw new IllegalArgumentException("initialBuffer is a read-only buffer."); + } + + int initialCapacity = initialBuffer.remaining(); + if (initialCapacity > maxCapacity) { + throw new IllegalArgumentException(String.format( + "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity)); + } + + this.alloc = alloc; + doNotFree = true; + setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN)); + writerIndex(initialCapacity); + } + + /** + * Allocate a new direct {@link ByteBuffer} with the given initialCapacity. + */ + protected ByteBuffer allocateDirect(int initialCapacity) { + return ByteBuffer.allocateDirect(initialCapacity); + } + + /** + * Free a direct {@link ByteBuffer} + */ + protected void freeDirect(ByteBuffer buffer) { + PlatformDependent.freeDirectBuffer(buffer); + } + + private void setByteBuffer(ByteBuffer buffer) { + ByteBuffer oldBuffer = this.buffer; + if (oldBuffer != null) { + if (doNotFree) { + doNotFree = false; + } else { + freeDirect(oldBuffer); + } + } + + this.buffer = buffer; + memoryAddress = PlatformDependent.directBufferAddress(buffer); + tmpNioBuf = null; + capacity = buffer.remaining(); + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public int capacity() { + return capacity; + } + + @Override + public ByteBuf capacity(int newCapacity) { + ensureAccessible(); + if (newCapacity < 0 || newCapacity > maxCapacity()) { + throw new IllegalArgumentException("newCapacity: " + newCapacity); + } + + int readerIndex = readerIndex(); + int writerIndex = writerIndex(); + + int oldCapacity = capacity; + if (newCapacity > oldCapacity) { + ByteBuffer oldBuffer = buffer; + ByteBuffer newBuffer = allocateDirect(newCapacity); + oldBuffer.position(0).limit(oldBuffer.capacity()); + newBuffer.position(0).limit(oldBuffer.capacity()); + newBuffer.put(oldBuffer); + newBuffer.clear(); + setByteBuffer(newBuffer); + } else if (newCapacity < oldCapacity) { + ByteBuffer oldBuffer = buffer; + ByteBuffer newBuffer = allocateDirect(newCapacity); + if (readerIndex < newCapacity) { + if (writerIndex > newCapacity) { + writerIndex(writerIndex = newCapacity); + } + oldBuffer.position(readerIndex).limit(writerIndex); + newBuffer.position(readerIndex).limit(writerIndex); + newBuffer.put(oldBuffer); + newBuffer.clear(); + } else { + setIndex(newCapacity, newCapacity); + } + setByteBuffer(newBuffer); + } + return this; + } + + @Override + public ByteBufAllocator alloc() { + return alloc; + } + + @Override + public ByteOrder order() { + return ByteOrder.BIG_ENDIAN; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public int arrayOffset() { + throw new UnsupportedOperationException("direct buffer"); + } + + @Override + public boolean hasMemoryAddress() { + return true; + } + + @Override + public long memoryAddress() { + return memoryAddress; + } + + @Override + protected byte _getByte(int index) { + return PlatformDependent.getByte(addr(index)); + } + + @Override + protected short _getShort(int index) { + short v = PlatformDependent.getShort(addr(index)); + return NATIVE_ORDER? v : Short.reverseBytes(v); + } + + @Override + protected int _getUnsignedMedium(int index) { + long addr = addr(index); + return (PlatformDependent.getByte(addr) & 0xff) << 16 | + (PlatformDependent.getByte(addr + 1) & 0xff) << 8 | + PlatformDependent.getByte(addr + 2) & 0xff; + } + + @Override + protected int _getInt(int index) { + int v = PlatformDependent.getInt(addr(index)); + return NATIVE_ORDER? v : Integer.reverseBytes(v); + } + + @Override + protected long _getLong(int index) { + long v = PlatformDependent.getLong(addr(index)); + return NATIVE_ORDER? v : Long.reverseBytes(v); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.capacity() - length) { + throw new IndexOutOfBoundsException("dstIndex: " + dstIndex); + } + + if (dst.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length); + } else if (dst.hasArray()) { + PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length); + } else { + dst.setBytes(dstIndex, this, index, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + checkIndex(index, length); + if (dst == null) { + throw new NullPointerException("dst"); + } + if (dstIndex < 0 || dstIndex > dst.length - length) { + throw new IndexOutOfBoundsException(String.format( + "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length)); + } + + if (length != 0) { + PlatformDependent.copyMemory(addr(index), dst, dstIndex, length); + } + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + getBytes(index, dst, false); + return this; + } + + private void getBytes(int index, ByteBuffer dst, boolean internal) { + checkIndex(index); + if (dst == null) { + throw new NullPointerException("dst"); + } + + int bytesToCopy = Math.min(capacity() - index, dst.remaining()); + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = buffer.duplicate(); + } + tmpBuf.clear().position(index).limit(index + bytesToCopy); + dst.put(tmpBuf); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + int length = dst.remaining(); + checkReadableBytes(length); + getBytes(readerIndex, dst, true); + readerIndex += length; + return this; + } + + @Override + protected void _setByte(int index, int value) { + PlatformDependent.putByte(addr(index), (byte) value); + } + + @Override + protected void _setShort(int index, int value) { + PlatformDependent.putShort(addr(index), NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value)); + } + + @Override + protected void _setMedium(int index, int value) { + long addr = addr(index); + PlatformDependent.putByte(addr, (byte) (value >>> 16)); + PlatformDependent.putByte(addr + 1, (byte) (value >>> 8)); + PlatformDependent.putByte(addr + 2, (byte) value); + } + + @Override + protected void _setInt(int index, int value) { + PlatformDependent.putInt(addr(index), NATIVE_ORDER ? value : Integer.reverseBytes(value)); + } + + @Override + protected void _setLong(int index, long value) { + PlatformDependent.putLong(addr(index), NATIVE_ORDER ? value : Long.reverseBytes(value)); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + checkIndex(index, length); + if (src == null) { + throw new NullPointerException("src"); + } + if (srcIndex < 0 || srcIndex > src.capacity() - length) { + throw new IndexOutOfBoundsException("srcIndex: " + srcIndex); + } + + if (length != 0) { + if (src.hasMemoryAddress()) { + PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr(index), length); + } else if (src.hasArray()) { + PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr(index), length); + } else { + src.getBytes(srcIndex, this, index, length); + } + } + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + checkIndex(index, length); + if (length != 0) { + PlatformDependent.copyMemory(src, srcIndex, addr(index), length); + } + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + ensureAccessible(); + ByteBuffer tmpBuf = internalNioBuffer(); + if (src == tmpBuf) { + src = src.duplicate(); + } + + tmpBuf.clear().position(index).limit(index + src.remaining()); + tmpBuf.put(src); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + ensureAccessible(); + if (length != 0) { + byte[] tmp = new byte[length]; + PlatformDependent.copyMemory(addr(index), tmp, 0, length); + out.write(tmp); + } + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return getBytes(index, out, length, false); + } + + private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException { + ensureAccessible(); + if (length == 0) { + return 0; + } + + ByteBuffer tmpBuf; + if (internal) { + tmpBuf = internalNioBuffer(); + } else { + tmpBuf = buffer.duplicate(); + } + tmpBuf.clear().position(index).limit(index + length); + return out.write(tmpBuf); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + checkReadableBytes(length); + int readBytes = getBytes(readerIndex, out, length, true); + readerIndex += readBytes; + return readBytes; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + checkIndex(index, length); + byte[] tmp = new byte[length]; + int readBytes = in.read(tmp); + if (readBytes > 0) { + PlatformDependent.copyMemory(tmp, 0, addr(index), readBytes); + } + return readBytes; + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + ensureAccessible(); + ByteBuffer tmpBuf = internalNioBuffer(); + tmpBuf.clear().position(index).limit(index + length); + try { + return in.read(tmpBuf); + } catch (ClosedChannelException ignored) { + return -1; + } + } + + @Override + public int nioBufferCount() { + return 1; + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return new ByteBuffer[] { nioBuffer(index, length) }; + } + + @Override + public ByteBuf copy(int index, int length) { + checkIndex(index, length); + ByteBuf copy = alloc().directBuffer(length, maxCapacity()); + if (length != 0) { + if (copy.hasMemoryAddress()) { + PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length); + copy.setIndex(0, length); + } else { + copy.writeBytes(this, index, length); + } + } + return copy; + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + checkIndex(index, length); + return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length); + } + + private ByteBuffer internalNioBuffer() { + ByteBuffer tmpNioBuf = this.tmpNioBuf; + if (tmpNioBuf == null) { + this.tmpNioBuf = tmpNioBuf = buffer.duplicate(); + } + return tmpNioBuf; + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + checkIndex(index, length); + return ((ByteBuffer) buffer.duplicate().position(index).limit(index + length)).slice(); + } + + @Override + protected void deallocate() { + ByteBuffer buffer = this.buffer; + if (buffer == null) { + return; + } + + this.buffer = null; + + if (!doNotFree) { + freeDirect(buffer); + } + } + + @Override + public ByteBuf unwrap() { + return null; + } + + long addr(int index) { + return memoryAddress + index; + } + + @Override + protected SwappedByteBuf newSwappedByteBuf() { + return new UnsafeDirectSwappedByteBuf(this); + } +} diff --git a/common/src/common/net/buffer/UnreleasableByteBuf.java b/common/src/common/net/buffer/UnreleasableByteBuf.java new file mode 100644 index 0000000..3a677ba --- /dev/null +++ b/common/src/common/net/buffer/UnreleasableByteBuf.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.nio.ByteOrder; + +/** + * A {@link ByteBuf} implementation that wraps another buffer to prevent a user from increasing or decreasing the + * wrapped buffer's reference count. + */ +final class UnreleasableByteBuf extends WrappedByteBuf { + + private SwappedByteBuf swappedBuf; + + UnreleasableByteBuf(ByteBuf buf) { + super(buf); + } + + @Override + public ByteBuf order(ByteOrder endianness) { + if (endianness == null) { + throw new NullPointerException("endianness"); + } + if (endianness == order()) { + return this; + } + + SwappedByteBuf swappedBuf = this.swappedBuf; + if (swappedBuf == null) { + this.swappedBuf = swappedBuf = new SwappedByteBuf(this); + } + return swappedBuf; + } + + @Override + public ByteBuf readSlice(int length) { + return new UnreleasableByteBuf(buf.readSlice(length)); + } + + @Override + public ByteBuf slice() { + return new UnreleasableByteBuf(buf.slice()); + } + + @Override + public ByteBuf slice(int index, int length) { + return new UnreleasableByteBuf(buf.slice(index, length)); + } + + @Override + public ByteBuf duplicate() { + return new UnreleasableByteBuf(buf.duplicate()); + } + + @Override + public ByteBuf retain(int increment) { + return this; + } + + @Override + public ByteBuf retain() { + return this; + } + + @Override + public boolean release() { + return false; + } + + @Override + public boolean release(int decrement) { + return false; + } +} diff --git a/common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java b/common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java new file mode 100644 index 0000000..8d5f55c --- /dev/null +++ b/common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java @@ -0,0 +1,186 @@ +/* +* Copyright 2014 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 common.net.buffer; + +import java.nio.ByteOrder; + +import common.net.util.internal.PlatformDependent; + +/** + * Special {@link SwappedByteBuf} for {@link ByteBuf}s that are backed by a {@code memoryAddress}. + */ +final class UnsafeDirectSwappedByteBuf extends SwappedByteBuf { + private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + private final boolean nativeByteOrder; + private final AbstractByteBuf wrapped; + + UnsafeDirectSwappedByteBuf(AbstractByteBuf buf) { + super(buf); + wrapped = buf; + nativeByteOrder = NATIVE_ORDER == (order() == ByteOrder.BIG_ENDIAN); + } + + private long addr(int index) { + // We need to call wrapped.memoryAddress() everytime and NOT cache it as it may change if the buffer expand. + // See: + // - https://github.com/netty/netty/issues/2587 + // - https://github.com/netty/netty/issues/2580 + return wrapped.memoryAddress() + index; + } + + @Override + public long getLong(int index) { + wrapped.checkIndex(index, 8); + long v = PlatformDependent.getLong(addr(index)); + return nativeByteOrder? v : Long.reverseBytes(v); + } + + @Override + public float getFloat(int index) { + return Float.intBitsToFloat(getInt(index)); + } + + @Override + public double getDouble(int index) { + return Double.longBitsToDouble(getLong(index)); + } + + @Override + public char getChar(int index) { + return (char) getShort(index); + } + + @Override + public long getUnsignedInt(int index) { + return getInt(index) & 0xFFFFFFFFL; + } + + @Override + public int getInt(int index) { + wrapped.checkIndex(index, 4); + int v = PlatformDependent.getInt(addr(index)); + return nativeByteOrder? v : Integer.reverseBytes(v); + } + + @Override + public int getUnsignedShort(int index) { + return getShort(index) & 0xFFFF; + } + + @Override + public short getShort(int index) { + wrapped.checkIndex(index, 2); + short v = PlatformDependent.getShort(addr(index)); + return nativeByteOrder? v : Short.reverseBytes(v); + } + + @Override + public ByteBuf setShort(int index, int value) { + wrapped.checkIndex(index, 2); + _setShort(index, value); + return this; + } + + @Override + public ByteBuf setInt(int index, int value) { + wrapped.checkIndex(index, 4); + _setInt(index, value); + return this; + } + + @Override + public ByteBuf setLong(int index, long value) { + wrapped.checkIndex(index, 8); + _setLong(index, value); + return this; + } + + @Override + public ByteBuf setChar(int index, int value) { + setShort(index, value); + return this; + } + + @Override + public ByteBuf setFloat(int index, float value) { + setInt(index, Float.floatToRawIntBits(value)); + return this; + } + + @Override + public ByteBuf setDouble(int index, double value) { + setLong(index, Double.doubleToRawLongBits(value)); + return this; + } + + @Override + public ByteBuf writeShort(int value) { + wrapped.ensureAccessible(); + wrapped.ensureWritable(2); + _setShort(wrapped.writerIndex, value); + wrapped.writerIndex += 2; + return this; + } + + @Override + public ByteBuf writeInt(int value) { + wrapped.ensureAccessible(); + wrapped.ensureWritable(4); + _setInt(wrapped.writerIndex, value); + wrapped.writerIndex += 4; + return this; + } + + @Override + public ByteBuf writeLong(long value) { + wrapped.ensureAccessible(); + wrapped.ensureWritable(8); + _setLong(wrapped.writerIndex, value); + wrapped.writerIndex += 8; + return this; + } + + @Override + public ByteBuf writeChar(int value) { + writeShort(value); + return this; + } + + @Override + public ByteBuf writeFloat(float value) { + writeInt(Float.floatToRawIntBits(value)); + return this; + } + + @Override + public ByteBuf writeDouble(double value) { + writeLong(Double.doubleToRawLongBits(value)); + return this; + } + + private void _setShort(int index, int value) { + PlatformDependent.putShort(addr(index), nativeByteOrder ? (short) value : Short.reverseBytes((short) value)); + } + + private void _setInt(int index, int value) { + PlatformDependent.putInt(addr(index), nativeByteOrder ? value : Integer.reverseBytes(value)); + } + + private void _setLong(int index, long value) { + PlatformDependent.putLong(addr(index), nativeByteOrder ? value : Long.reverseBytes(value)); + } +} diff --git a/common/src/common/net/buffer/WrappedByteBuf.java b/common/src/common/net/buffer/WrappedByteBuf.java new file mode 100644 index 0000000..c135c77 --- /dev/null +++ b/common/src/common/net/buffer/WrappedByteBuf.java @@ -0,0 +1,827 @@ +/* + * Copyright 2013 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 common.net.buffer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; + +import common.net.util.internal.StringUtil; + +class WrappedByteBuf extends ByteBuf { + + protected final ByteBuf buf; + + protected WrappedByteBuf(ByteBuf buf) { + if (buf == null) { + throw new NullPointerException("buf"); + } + this.buf = buf; + } + + @Override + public boolean hasMemoryAddress() { + return buf.hasMemoryAddress(); + } + + @Override + public long memoryAddress() { + return buf.memoryAddress(); + } + + @Override + public int capacity() { + return buf.capacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + buf.capacity(newCapacity); + return this; + } + + @Override + public int maxCapacity() { + return buf.maxCapacity(); + } + + @Override + public ByteBufAllocator alloc() { + return buf.alloc(); + } + + @Override + public ByteOrder order() { + return buf.order(); + } + + @Override + public ByteBuf order(ByteOrder endianness) { + return buf.order(endianness); + } + + @Override + public ByteBuf unwrap() { + return buf; + } + + @Override + public boolean isDirect() { + return buf.isDirect(); + } + + @Override + public int readerIndex() { + return buf.readerIndex(); + } + + @Override + public ByteBuf readerIndex(int readerIndex) { + buf.readerIndex(readerIndex); + return this; + } + + @Override + public int writerIndex() { + return buf.writerIndex(); + } + + @Override + public ByteBuf writerIndex(int writerIndex) { + buf.writerIndex(writerIndex); + return this; + } + + @Override + public ByteBuf setIndex(int readerIndex, int writerIndex) { + buf.setIndex(readerIndex, writerIndex); + return this; + } + + @Override + public int readableBytes() { + return buf.readableBytes(); + } + + @Override + public int writableBytes() { + return buf.writableBytes(); + } + + @Override + public int maxWritableBytes() { + return buf.maxWritableBytes(); + } + + @Override + public boolean isReadable() { + return buf.isReadable(); + } + + @Override + public boolean isWritable() { + return buf.isWritable(); + } + + @Override + public ByteBuf clear() { + buf.clear(); + return this; + } + + @Override + public ByteBuf markReaderIndex() { + buf.markReaderIndex(); + return this; + } + + @Override + public ByteBuf resetReaderIndex() { + buf.resetReaderIndex(); + return this; + } + + @Override + public ByteBuf markWriterIndex() { + buf.markWriterIndex(); + return this; + } + + @Override + public ByteBuf resetWriterIndex() { + buf.resetWriterIndex(); + return this; + } + + @Override + public ByteBuf discardReadBytes() { + buf.discardReadBytes(); + return this; + } + + @Override + public ByteBuf discardSomeReadBytes() { + buf.discardSomeReadBytes(); + return this; + } + + @Override + public ByteBuf ensureWritable(int minWritableBytes) { + buf.ensureWritable(minWritableBytes); + return this; + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + return buf.ensureWritable(minWritableBytes, force); + } + + @Override + public boolean getBoolean(int index) { + return buf.getBoolean(index); + } + + @Override + public byte getByte(int index) { + return buf.getByte(index); + } + + @Override + public short getUnsignedByte(int index) { + return buf.getUnsignedByte(index); + } + + @Override + public short getShort(int index) { + return buf.getShort(index); + } + + @Override + public int getUnsignedShort(int index) { + return buf.getUnsignedShort(index); + } + + @Override + public int getMedium(int index) { + return buf.getMedium(index); + } + + @Override + public int getUnsignedMedium(int index) { + return buf.getUnsignedMedium(index); + } + + @Override + public int getInt(int index) { + return buf.getInt(index); + } + + @Override + public long getUnsignedInt(int index) { + return buf.getUnsignedInt(index); + } + + @Override + public long getLong(int index) { + return buf.getLong(index); + } + + @Override + public char getChar(int index) { + return buf.getChar(index); + } + + @Override + public float getFloat(int index) { + return buf.getFloat(index); + } + + @Override + public double getDouble(int index) { + return buf.getDouble(index); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst) { + buf.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int length) { + buf.getBytes(index, dst, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + buf.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst) { + buf.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + buf.getBytes(index, dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + buf.getBytes(index, dst); + return this; + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + buf.getBytes(index, out, length); + return this; + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return buf.getBytes(index, out, length); + } + + @Override + public ByteBuf setBoolean(int index, boolean value) { + buf.setBoolean(index, value); + return this; + } + + @Override + public ByteBuf setByte(int index, int value) { + buf.setByte(index, value); + return this; + } + + @Override + public ByteBuf setShort(int index, int value) { + buf.setShort(index, value); + return this; + } + + @Override + public ByteBuf setMedium(int index, int value) { + buf.setMedium(index, value); + return this; + } + + @Override + public ByteBuf setInt(int index, int value) { + buf.setInt(index, value); + return this; + } + + @Override + public ByteBuf setLong(int index, long value) { + buf.setLong(index, value); + return this; + } + + @Override + public ByteBuf setChar(int index, int value) { + buf.setChar(index, value); + return this; + } + + @Override + public ByteBuf setFloat(int index, float value) { + buf.setFloat(index, value); + return this; + } + + @Override + public ByteBuf setDouble(int index, double value) { + buf.setDouble(index, value); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src) { + buf.setBytes(index, src); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int length) { + buf.setBytes(index, src, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + buf.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src) { + buf.setBytes(index, src); + return this; + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + buf.setBytes(index, src, srcIndex, length); + return this; + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + buf.setBytes(index, src); + return this; + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + return buf.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + return buf.setBytes(index, in, length); + } + + @Override + public ByteBuf setZero(int index, int length) { + buf.setZero(index, length); + return this; + } + + @Override + public boolean readBoolean() { + return buf.readBoolean(); + } + + @Override + public byte readByte() { + return buf.readByte(); + } + + @Override + public short readUnsignedByte() { + return buf.readUnsignedByte(); + } + + @Override + public short readShort() { + return buf.readShort(); + } + + @Override + public int readUnsignedShort() { + return buf.readUnsignedShort(); + } + + @Override + public int readMedium() { + return buf.readMedium(); + } + + @Override + public int readUnsignedMedium() { + return buf.readUnsignedMedium(); + } + + @Override + public int readInt() { + return buf.readInt(); + } + + @Override + public long readUnsignedInt() { + return buf.readUnsignedInt(); + } + + @Override + public long readLong() { + return buf.readLong(); + } + + @Override + public char readChar() { + return buf.readChar(); + } + + @Override + public float readFloat() { + return buf.readFloat(); + } + + @Override + public double readDouble() { + return buf.readDouble(); + } + + @Override + public ByteBuf readBytes(int length) { + return buf.readBytes(length); + } + + @Override + public ByteBuf readSlice(int length) { + return buf.readSlice(length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst) { + buf.readBytes(dst); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int length) { + buf.readBytes(dst, length); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + buf.readBytes(dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf readBytes(byte[] dst) { + buf.readBytes(dst); + return this; + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + buf.readBytes(dst, dstIndex, length); + return this; + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + buf.readBytes(dst); + return this; + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + buf.readBytes(out, length); + return this; + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + return buf.readBytes(out, length); + } + + @Override + public ByteBuf skipBytes(int length) { + buf.skipBytes(length); + return this; + } + + @Override + public ByteBuf writeBoolean(boolean value) { + buf.writeBoolean(value); + return this; + } + + @Override + public ByteBuf writeByte(int value) { + buf.writeByte(value); + return this; + } + + @Override + public ByteBuf writeShort(int value) { + buf.writeShort(value); + return this; + } + + @Override + public ByteBuf writeMedium(int value) { + buf.writeMedium(value); + return this; + } + + @Override + public ByteBuf writeInt(int value) { + buf.writeInt(value); + return this; + } + + @Override + public ByteBuf writeLong(long value) { + buf.writeLong(value); + return this; + } + + @Override + public ByteBuf writeChar(int value) { + buf.writeChar(value); + return this; + } + + @Override + public ByteBuf writeFloat(float value) { + buf.writeFloat(value); + return this; + } + + @Override + public ByteBuf writeDouble(double value) { + buf.writeDouble(value); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src) { + buf.writeBytes(src); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int length) { + buf.writeBytes(src, length); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + buf.writeBytes(src, srcIndex, length); + return this; + } + + @Override + public ByteBuf writeBytes(byte[] src) { + buf.writeBytes(src); + return this; + } + + @Override + public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { + buf.writeBytes(src, srcIndex, length); + return this; + } + + @Override + public ByteBuf writeBytes(ByteBuffer src) { + buf.writeBytes(src); + return this; + } + + @Override + public int writeBytes(InputStream in, int length) throws IOException { + return buf.writeBytes(in, length); + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + return buf.writeBytes(in, length); + } + + @Override + public ByteBuf writeZero(int length) { + buf.writeZero(length); + return this; + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + return buf.indexOf(fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + return buf.bytesBefore(value); + } + + @Override + public int bytesBefore(int length, byte value) { + return buf.bytesBefore(length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + return buf.bytesBefore(index, length, value); + } + + @Override + public int forEachByte(ByteBufProcessor processor) { + return buf.forEachByte(processor); + } + + @Override + public int forEachByte(int index, int length, ByteBufProcessor processor) { + return buf.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(ByteBufProcessor processor) { + return buf.forEachByteDesc(processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteBufProcessor processor) { + return buf.forEachByteDesc(index, length, processor); + } + + @Override + public ByteBuf copy() { + return buf.copy(); + } + + @Override + public ByteBuf copy(int index, int length) { + return buf.copy(index, length); + } + + @Override + public ByteBuf slice() { + return buf.slice(); + } + + @Override + public ByteBuf slice(int index, int length) { + return buf.slice(index, length); + } + + @Override + public ByteBuf duplicate() { + return buf.duplicate(); + } + + @Override + public int nioBufferCount() { + return buf.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer() { + return buf.nioBuffer(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return buf.nioBuffer(index, length); + } + + @Override + public ByteBuffer[] nioBuffers() { + return buf.nioBuffers(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return buf.nioBuffers(index, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return buf.internalNioBuffer(index, length); + } + + @Override + public boolean hasArray() { + return buf.hasArray(); + } + + @Override + public byte[] array() { + return buf.array(); + } + + @Override + public int arrayOffset() { + return buf.arrayOffset(); + } + + @Override + public String toString(Charset charset) { + return buf.toString(charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + return buf.toString(index, length, charset); + } + + @Override + public int hashCode() { + return buf.hashCode(); + } + + @Override + + public boolean equals(Object obj) { + return buf.equals(obj); + } + + @Override + public int compareTo(ByteBuf buffer) { + return buf.compareTo(buffer); + } + + @Override + public String toString() { + return StringUtil.simpleClassName(this) + '(' + buf.toString() + ')'; + } + + @Override + public ByteBuf retain(int increment) { + buf.retain(increment); + return this; + } + + @Override + public ByteBuf retain() { + buf.retain(); + return this; + } + + @Override + public boolean isReadable(int size) { + return buf.isReadable(size); + } + + @Override + public boolean isWritable(int size) { + return buf.isWritable(size); + } + + @Override + public int refCnt() { + return buf.refCnt(); + } + + @Override + public boolean release() { + return buf.release(); + } + + @Override + public boolean release(int decrement) { + return buf.release(decrement); + } +} diff --git a/common/src/common/net/channel/AbstractChannel.java b/common/src/common/net/channel/AbstractChannel.java new file mode 100644 index 0000000..031f273 --- /dev/null +++ b/common/src/common/net/channel/AbstractChannel.java @@ -0,0 +1,873 @@ +/* + * 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 common.net.channel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NotYetConnectedException; +import java.util.concurrent.RejectedExecutionException; + +import common.net.buffer.ByteBufAllocator; +import common.net.util.DefaultAttributeMap; +import common.net.util.ReferenceCountUtil; +import common.net.util.internal.EmptyArrays; +import common.net.util.internal.OneTimeTask; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.ThreadLocalRandom; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A skeletal {@link Channel} implementation. + */ +public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class); + + static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException(); + static final NotYetConnectedException NOT_YET_CONNECTED_EXCEPTION = new NotYetConnectedException(); + + static { + CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); + NOT_YET_CONNECTED_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); + } + + private MessageSizeEstimator.Handle estimatorHandle; + + private final Channel parent; + private final long hashCode = ThreadLocalRandom.current().nextLong(); + private final Unsafe unsafe; + private final DefaultChannelPipeline pipeline; + private final ChannelFuture succeededFuture = new SucceededChannelFuture(this, null); + private final VoidChannelPromise voidPromise = new VoidChannelPromise(this, true); + private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false); + private final CloseFuture closeFuture = new CloseFuture(this); + + private volatile SocketAddress localAddress; + private volatile SocketAddress remoteAddress; + private volatile EventLoop eventLoop; + private volatile boolean registered; + + /** Cache for the string representation of this channel */ + private boolean strValActive; + private String strVal; + + /** + * Creates a new instance. + * + * @param parent + * the parent of this channel. {@code null} if there's no parent. + */ + protected AbstractChannel(Channel parent) { + this.parent = parent; + unsafe = newUnsafe(); + pipeline = new DefaultChannelPipeline(this); + } + + @Override + public boolean isWritable() { + ChannelOutboundBuffer buf = unsafe.outboundBuffer(); + return buf != null && buf.isWritable(); + } + + @Override + public Channel parent() { + return parent; + } + + @Override + public ChannelPipeline pipeline() { + return pipeline; + } + + @Override + public ByteBufAllocator alloc() { + return config().getAllocator(); + } + + @Override + public EventLoop eventLoop() { + EventLoop eventLoop = this.eventLoop; + if (eventLoop == null) { + throw new IllegalStateException("channel not registered to an event loop"); + } + return eventLoop; + } + + @Override + public SocketAddress localAddress() { + SocketAddress localAddress = this.localAddress; + if (localAddress == null) { + try { + this.localAddress = localAddress = unsafe().localAddress(); + } catch (Throwable t) { + // Sometimes fails on a closed socket in Windows. + return null; + } + } + return localAddress; + } + + protected void invalidateLocalAddress() { + localAddress = null; + } + + @Override + public SocketAddress remoteAddress() { + SocketAddress remoteAddress = this.remoteAddress; + if (remoteAddress == null) { + try { + this.remoteAddress = remoteAddress = unsafe().remoteAddress(); + } catch (Throwable t) { + // Sometimes fails on a closed socket in Windows. + return null; + } + } + return remoteAddress; + } + + /** + * Reset the stored remoteAddress + */ + protected void invalidateRemoteAddress() { + remoteAddress = null; + } + + @Override + public boolean isRegistered() { + return registered; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return pipeline.bind(localAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return pipeline.connect(remoteAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return pipeline.connect(remoteAddress, localAddress); + } + + @Override + public ChannelFuture disconnect() { + return pipeline.disconnect(); + } + + @Override + public ChannelFuture close() { + return pipeline.close(); + } + + @Override + public ChannelFuture deregister() { + return pipeline.deregister(); + } + + @Override + public Channel flush() { + pipeline.flush(); + return this; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return pipeline.bind(localAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return pipeline.connect(remoteAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return pipeline.connect(remoteAddress, localAddress, promise); + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return pipeline.disconnect(promise); + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return pipeline.close(promise); + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return pipeline.deregister(promise); + } + + @Override + public Channel read() { + pipeline.read(); + return this; + } + + @Override + public ChannelFuture write(Object msg) { + return pipeline.write(msg); + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return pipeline.write(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return pipeline.writeAndFlush(msg); + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return pipeline.writeAndFlush(msg, promise); + } + + @Override + public ChannelPromise newPromise() { + return new DefaultChannelPromise(this); + } + +// @Override +// public ChannelProgressivePromise newProgressivePromise() { +// return new DefaultChannelProgressivePromise(this); +// } + + @Override + public ChannelFuture newSucceededFuture() { + return succeededFuture; + } + + @Override + public ChannelFuture newFailedFuture(Throwable cause) { + return new FailedChannelFuture(this, null, cause); + } + + @Override + public ChannelFuture closeFuture() { + return closeFuture; + } + + @Override + public Unsafe unsafe() { + return unsafe; + } + + /** + * Create a new {@link AbstractUnsafe} instance which will be used for the life-time of the {@link Channel} + */ + protected abstract AbstractUnsafe newUnsafe(); + + /** + * Returns the ID of this channel. + */ + @Override + public final int hashCode() { + return (int) hashCode; + } + + /** + * Returns {@code true} if and only if the specified object is identical + * with this channel (i.e: {@code this == o}). + */ + @Override + public final boolean equals(Object o) { + return this == o; + } + + @Override + public final int compareTo(Channel o) { + if (this == o) { + return 0; + } + + long ret = hashCode - o.hashCode(); + if (ret > 0) { + return 1; + } + if (ret < 0) { + return -1; + } + + ret = System.identityHashCode(this) - System.identityHashCode(o); + if (ret != 0) { + return (int) ret; + } + + // Jackpot! - different objects with same hashes + throw new Error(); + } + + /** + * Returns the {@link String} representation of this channel. The returned + * string contains the {@linkplain #hashCode()} ID}, {@linkplain #localAddress() local address}, + * and {@linkplain #remoteAddress() remote address} of this channel for + * easier identification. + */ + @Override + public String toString() { + boolean active = isActive(); + if (strValActive == active && strVal != null) { + return strVal; + } + + SocketAddress remoteAddr = remoteAddress(); + SocketAddress localAddr = localAddress(); + if (remoteAddr != null) { + SocketAddress srcAddr; + SocketAddress dstAddr; + if (parent == null) { + srcAddr = localAddr; + dstAddr = remoteAddr; + } else { + srcAddr = remoteAddr; + dstAddr = localAddr; + } + strVal = String.format("[id: 0x%08x, %s %s %s]", (int) hashCode, srcAddr, active? "=>" : ":>", dstAddr); + } else if (localAddr != null) { + strVal = String.format("[id: 0x%08x, %s]", (int) hashCode, localAddr); + } else { + strVal = String.format("[id: 0x%08x]", (int) hashCode); + } + + strValActive = active; + return strVal; + } + + @Override + public final ChannelPromise voidPromise() { + return voidPromise; + } + + final MessageSizeEstimator.Handle estimatorHandle() { + if (estimatorHandle == null) { + estimatorHandle = config().getMessageSizeEstimator().newHandle(); + } + return estimatorHandle; + } + + /** + * {@link Unsafe} implementation which sub-classes must extend and use. + */ + protected abstract class AbstractUnsafe implements Unsafe { + + private ChannelOutboundBuffer outboundBuffer = new ChannelOutboundBuffer(AbstractChannel.this); + private boolean inFlush0; + + @Override + public final ChannelOutboundBuffer outboundBuffer() { + return outboundBuffer; + } + + @Override + public final SocketAddress localAddress() { + return localAddress0(); + } + + @Override + public final SocketAddress remoteAddress() { + return remoteAddress0(); + } + + @Override + public final void register(EventLoop eventLoop, final ChannelPromise promise) { + if (eventLoop == null) { + throw new NullPointerException("eventLoop"); + } + if (isRegistered()) { + promise.setFailure(new IllegalStateException("registered to an event loop already")); + return; + } + if (!isCompatible(eventLoop)) { + promise.setFailure( + new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); + return; + } + + AbstractChannel.this.eventLoop = eventLoop; + + if (eventLoop.inEventLoop()) { + register0(promise); + } else { + try { + eventLoop.execute(new OneTimeTask() { + @Override + public void run() { + register0(promise); + } + }); + } catch (Throwable t) { + logger.warn( + "Force-closing a channel whose registration task was not accepted by an event loop: {}", + AbstractChannel.this, t); + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + } + + private void register0(ChannelPromise promise) { + try { + // check if the channel is still open as it could be closed in the mean time when the register + // call was outside of the eventLoop + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + doRegister(); + registered = true; + safeSetSuccess(promise); + pipeline.fireChannelRegistered(); + if (isActive()) { + pipeline.fireChannelActive(); + } + } catch (Throwable t) { + // Close the channel directly to avoid FD leak. + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + + @Override + public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + // See: https://github.com/netty/netty/issues/576 + if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() && + Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) && + localAddress instanceof InetSocketAddress && + !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) { + // Warn a user about the fact that a non-root user can't receive a + // broadcast packet on *nix if the socket is bound on non-wildcard address. + logger.warn( + "A non-root user can't receive a broadcast packet if the socket " + + "is not bound to a wildcard address; binding to a non-wildcard " + + "address (" + localAddress + ") anyway as requested."); + } + + boolean wasActive = isActive(); + try { + doBind(localAddress); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (!wasActive && isActive()) { + invokeLater(new OneTimeTask() { + @Override + public void run() { + pipeline.fireChannelActive(); + } + }); + } + + safeSetSuccess(promise); + } + + @Override + public final void disconnect(final ChannelPromise promise) { + if (!promise.setUncancellable()) { + return; + } + + boolean wasActive = isActive(); + try { + doDisconnect(); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (wasActive && !isActive()) { + invokeLater(new OneTimeTask() { + @Override + public void run() { + pipeline.fireChannelInactive(); + } + }); + } + + safeSetSuccess(promise); + closeIfClosed(); // doDisconnect() might have closed the channel + } + + @Override + public final void close(final ChannelPromise promise) { + if (!promise.setUncancellable()) { + return; + } + + if (inFlush0) { + invokeLater(new OneTimeTask() { + @Override + public void run() { + close(promise); + } + }); + return; + } + + if (closeFuture.isDone()) { + // Closed already. + safeSetSuccess(promise); + return; + } + + boolean wasActive = isActive(); + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer. + + try { + doClose(); + closeFuture.setClosed(); + safeSetSuccess(promise); + } catch (Throwable t) { + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + + // Fail all the queued messages + try { + outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION); + outboundBuffer.close(CLOSED_CHANNEL_EXCEPTION); + } finally { + + if (wasActive && !isActive()) { + invokeLater(new OneTimeTask() { + @Override + public void run() { + pipeline.fireChannelInactive(); + } + }); + } + + deregister(voidPromise()); + } + } + + @Override + public final void closeForcibly() { + try { + doClose(); + } catch (Exception e) { + logger.warn("Failed to close a channel.", e); + } + } + + @Override + public final void deregister(final ChannelPromise promise) { + if (!promise.setUncancellable()) { + return; + } + + if (!registered) { + safeSetSuccess(promise); + return; + } + + try { + doDeregister(); + } catch (Throwable t) { + logger.warn("Unexpected exception occurred while deregistering a channel.", t); + } finally { + if (registered) { + registered = false; + invokeLater(new OneTimeTask() { + @Override + public void run() { + pipeline.fireChannelUnregistered(); + } + }); + safeSetSuccess(promise); + } else { + // Some transports like local and AIO does not allow the deregistration of + // an open channel. Their doDeregister() calls close(). Consequently, + // close() calls deregister() again - no need to fire channelUnregistered. + safeSetSuccess(promise); + } + } + } + + @Override + public final void beginRead() { + if (!isActive()) { + return; + } + + try { + doBeginRead(); + } catch (final Exception e) { + invokeLater(new OneTimeTask() { + @Override + public void run() { + pipeline.fireExceptionCaught(e); + } + }); + close(voidPromise()); + } + } + + @Override + public final void write(Object msg, ChannelPromise promise) { + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null) { + // If the outboundBuffer is null we know the channel was closed and so + // need to fail the future right away. If it is not null the handling of the rest + // will be done in flush0() + // See https://github.com/netty/netty/issues/2362 + safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION); + // release message now to prevent resource-leak + ReferenceCountUtil.release(msg); + return; + } + + int size; + try { + msg = filterOutboundMessage(msg); + size = estimatorHandle().size(msg); + if (size < 0) { + size = 0; + } + } catch (Throwable t) { + safeSetFailure(promise, t); + ReferenceCountUtil.release(msg); + return; + } + + outboundBuffer.addMessage(msg, size, promise); + } + + @Override + public final void flush() { + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null) { + return; + } + + outboundBuffer.addFlush(); + flush0(); + } + + protected void flush0() { + if (inFlush0) { + // Avoid re-entrance + return; + } + + final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null || outboundBuffer.isEmpty()) { + return; + } + + inFlush0 = true; + + // Mark all pending write requests as failure if the channel is inactive. + if (!isActive()) { + try { + if (isOpen()) { + outboundBuffer.failFlushed(NOT_YET_CONNECTED_EXCEPTION); + } else { + outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION); + } + } finally { + inFlush0 = false; + } + return; + } + + try { + doWrite(outboundBuffer); + } catch (Throwable t) { + outboundBuffer.failFlushed(t); + if (t instanceof IOException && config().isAutoClose()) { + close(voidPromise()); + } + } finally { + inFlush0 = false; + } + } + + @Override + public final ChannelPromise voidPromise() { + return unsafeVoidPromise; + } + + protected final boolean ensureOpen(ChannelPromise promise) { + if (isOpen()) { + return true; + } + + safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION); + return false; + } + + /** + * Marks the specified {@code promise} as success. If the {@code promise} is done already, log a message. + */ + protected final void safeSetSuccess(ChannelPromise promise) { + if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) { + logger.warn("Failed to mark a promise as success because it is done already: {}", promise); + } + } + + /** + * Marks the specified {@code promise} as failure. If the {@code promise} is done already, log a message. + */ + protected final void safeSetFailure(ChannelPromise promise, Throwable cause) { + if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) { + logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause); + } + } + + protected final void closeIfClosed() { + if (isOpen()) { + return; + } + close(voidPromise()); + } + + private void invokeLater(Runnable task) { + try { + // This method is used by outbound operation implementations to trigger an inbound event later. + // They do not trigger an inbound event immediately because an outbound operation might have been + // triggered by another inbound event handler method. If fired immediately, the call stack + // will look like this for example: + // + // handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection. + // -> handlerA.ctx.close() + // -> channel.unsafe.close() + // -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet + // + // which means the execution of two inbound handler methods of the same handler overlap undesirably. + eventLoop().execute(task); + } catch (RejectedExecutionException e) { + logger.warn("Can't invoke task later as EventLoop rejected it", e); + } + } + } + + /** + * Return {@code true} if the given {@link EventLoop} is compatible with this instance. + */ + protected abstract boolean isCompatible(EventLoop loop); + + /** + * Returns the {@link SocketAddress} which is bound locally. + */ + protected abstract SocketAddress localAddress0(); + + /** + * Return the {@link SocketAddress} which the {@link Channel} is connected to. + */ + protected abstract SocketAddress remoteAddress0(); + + /** + * Is called after the {@link Channel} is registered with its {@link EventLoop} as part of the register process. + * + * Sub-classes may override this method + */ + protected void doRegister() throws Exception { + // NOOP + } + + /** + * Bind the {@link Channel} to the {@link SocketAddress} + */ + protected abstract void doBind(SocketAddress localAddress) throws Exception; + + /** + * Disconnect this {@link Channel} from its remote peer + */ + protected abstract void doDisconnect() throws Exception; + + /** + * Close the {@link Channel} + */ + protected abstract void doClose() throws Exception; + + /** + * Deregister the {@link Channel} from its {@link EventLoop}. + * + * Sub-classes may override this method + */ + protected void doDeregister() throws Exception { + // NOOP + } + + /** + * Schedule a read operation. + */ + protected abstract void doBeginRead() throws Exception; + + /** + * Flush the content of the given buffer to the remote peer. + */ + protected abstract void doWrite(ChannelOutboundBuffer in) throws Exception; + + /** + * Invoked when a new message is added to a {@link ChannelOutboundBuffer} of this {@link AbstractChannel}, so that + * the {@link Channel} implementation converts the message to another. (e.g. heap buffer -> direct buffer) + */ + protected Object filterOutboundMessage(Object msg) throws Exception { + return msg; + } + + static final class CloseFuture extends DefaultChannelPromise { + + CloseFuture(AbstractChannel ch) { + super(ch); + } + + @Override + public ChannelPromise setSuccess() { + throw new IllegalStateException(); + } + + @Override + public ChannelPromise setFailure(Throwable cause) { + throw new IllegalStateException(); + } + + @Override + public boolean trySuccess() { + throw new IllegalStateException(); + } + + @Override + public boolean tryFailure(Throwable cause) { + throw new IllegalStateException(); + } + + boolean setClosed() { + return super.trySuccess(); + } + } +} diff --git a/common/src/common/net/channel/AbstractChannelHandlerContext.java b/common/src/common/net/channel/AbstractChannelHandlerContext.java new file mode 100644 index 0000000..204bd7a --- /dev/null +++ b/common/src/common/net/channel/AbstractChannelHandlerContext.java @@ -0,0 +1,1000 @@ +/* + * 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 common.net.channel; + +import static common.net.channel.DefaultChannelPipeline.logger; + +import java.net.SocketAddress; + +import common.net.buffer.ByteBufAllocator; +import common.net.util.DefaultAttributeMap; +import common.net.util.Recycler; +import common.net.util.ReferenceCountUtil; +import common.net.util.concurrent.EventExecutor; +import common.net.util.concurrent.EventExecutorGroup; +import common.net.util.internal.OneTimeTask; +import common.net.util.internal.RecyclableMpscLinkedQueueNode; +import common.net.util.internal.StringUtil; + +abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext { + + volatile AbstractChannelHandlerContext next; + volatile AbstractChannelHandlerContext prev; + + private final boolean inbound; + private final boolean outbound; + private final AbstractChannel channel; + private final DefaultChannelPipeline pipeline; + private final String name; + private boolean removed; + + // Will be set to null if no child executor should be used, otherwise it will be set to the + // child executor. + final EventExecutor executor; + private ChannelFuture succeededFuture; + + // Lazily instantiated tasks used to trigger events to a handler with different executor. + // These needs to be volatile as otherwise an other Thread may see an half initialized instance. + // See the JMM for more details + private volatile Runnable invokeChannelReadCompleteTask; + private volatile Runnable invokeReadTask; + private volatile Runnable invokeChannelWritableStateChangedTask; + private volatile Runnable invokeFlushTask; + + AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, + boolean inbound, boolean outbound) { + + if (name == null) { + throw new NullPointerException("name"); + } + + channel = pipeline.channel; + this.pipeline = pipeline; + this.name = name; + + if (group != null) { + // Pin one of the child executors once and remember it so that the same child executor + // is used to fire events for the same channel. + EventExecutor childExecutor = pipeline.childExecutors.get(group); + if (childExecutor == null) { + childExecutor = group.next(); + pipeline.childExecutors.put(group, childExecutor); + } + executor = childExecutor; + } else { + executor = null; + } + + this.inbound = inbound; + this.outbound = outbound; + } + + /** Invocation initiated by {@link DefaultChannelPipeline#teardownAll()}}. */ + void teardown() { + EventExecutor executor = executor(); + if (executor.inEventLoop()) { + teardown0(); + } else { + executor.execute(new Runnable() { + @Override + public void run() { + teardown0(); + } + }); + } + } + + private void teardown0() { + AbstractChannelHandlerContext prev = this.prev; + if (prev != null) { + synchronized (pipeline) { + pipeline.remove0(this); + } + prev.teardown(); + } + } + + @Override + public Channel channel() { + return channel; + } + + @Override + public ChannelPipeline pipeline() { + return pipeline; + } + + @Override + public ByteBufAllocator alloc() { + return channel().config().getAllocator(); + } + + @Override + public EventExecutor executor() { + if (executor == null) { + return channel().eventLoop(); + } else { + return executor; + } + } + + @Override + public String name() { + return name; + } + + @Override + public ChannelHandlerContext fireChannelRegistered() { + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelRegistered(); + } else { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeChannelRegistered(); + } + }); + } + return this; + } + + private void invokeChannelRegistered() { + try { + ((ChannelInboundHandler) handler()).channelRegistered(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireChannelUnregistered() { + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelUnregistered(); + } else { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeChannelUnregistered(); + } + }); + } + return this; + } + + private void invokeChannelUnregistered() { + try { + ((ChannelInboundHandler) handler()).channelUnregistered(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireChannelActive() { + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelActive(); + } else { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeChannelActive(); + } + }); + } + return this; + } + + private void invokeChannelActive() { + try { + ((ChannelInboundHandler) handler()).channelActive(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireChannelInactive() { + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelInactive(); + } else { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeChannelInactive(); + } + }); + } + return this; + } + + private void invokeChannelInactive() { + try { + ((ChannelInboundHandler) handler()).channelInactive(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireExceptionCaught(final Throwable cause) { + if (cause == null) { + throw new NullPointerException("cause"); + } + + final AbstractChannelHandlerContext next = this.next; + + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeExceptionCaught(cause); + } else { + try { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeExceptionCaught(cause); + } + }); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to submit an exceptionCaught() event.", t); + logger.warn("The exceptionCaught() event that was failed to submit was:", cause); + } + } + } + + return this; + } + + private void invokeExceptionCaught(final Throwable cause) { + try { + handler().exceptionCaught(this, cause); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by a user handler's " + + "exceptionCaught() method while handling the following exception:", cause); + } + } + } + + @Override + public ChannelHandlerContext fireUserEventTriggered(final Object event) { + if (event == null) { + throw new NullPointerException("event"); + } + + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeUserEventTriggered(event); + } else { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeUserEventTriggered(event); + } + }); + } + return this; + } + + private void invokeUserEventTriggered(Object event) { + try { + ((ChannelInboundHandler) handler()).userEventTriggered(this, event); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireChannelRead(final Object msg) { + if (msg == null) { + throw new NullPointerException("msg"); + } + + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelRead(msg); + } else { + executor.execute(new OneTimeTask() { + @Override + public void run() { + next.invokeChannelRead(msg); + } + }); + } + return this; + } + + private void invokeChannelRead(Object msg) { + try { + ((ChannelInboundHandler) handler()).channelRead(this, msg); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireChannelReadComplete() { + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelReadComplete(); + } else { + Runnable task = next.invokeChannelReadCompleteTask; + if (task == null) { + next.invokeChannelReadCompleteTask = task = new Runnable() { + @Override + public void run() { + next.invokeChannelReadComplete(); + } + }; + } + executor.execute(task); + } + return this; + } + + private void invokeChannelReadComplete() { + try { + ((ChannelInboundHandler) handler()).channelReadComplete(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelHandlerContext fireChannelWritabilityChanged() { + final AbstractChannelHandlerContext next = findContextInbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeChannelWritabilityChanged(); + } else { + Runnable task = next.invokeChannelWritableStateChangedTask; + if (task == null) { + next.invokeChannelWritableStateChangedTask = task = new Runnable() { + @Override + public void run() { + next.invokeChannelWritabilityChanged(); + } + }; + } + executor.execute(task); + } + return this; + } + + private void invokeChannelWritabilityChanged() { + try { + ((ChannelInboundHandler) handler()).channelWritabilityChanged(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return bind(localAddress, newPromise()); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return connect(remoteAddress, newPromise()); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return connect(remoteAddress, localAddress, newPromise()); + } + + @Override + public ChannelFuture disconnect() { + return disconnect(newPromise()); + } + + @Override + public ChannelFuture close() { + return close(newPromise()); + } + + @Override + public ChannelFuture deregister() { + return deregister(newPromise()); + } + + @Override + public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) { + if (localAddress == null) { + throw new NullPointerException("localAddress"); + } + if (!validatePromise(promise, false)) { + // cancelled + return promise; + } + + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeBind(localAddress, promise); + } else { + safeExecute(executor, new OneTimeTask() { + @Override + public void run() { + next.invokeBind(localAddress, promise); + } + }, promise, null); + } + + return promise; + } + + private void invokeBind(SocketAddress localAddress, ChannelPromise promise) { + try { + ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise); + } catch (Throwable t) { + notifyOutboundHandlerException(t, promise); + } + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return connect(remoteAddress, null, promise); + } + + @Override + public ChannelFuture connect( + final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { + + if (remoteAddress == null) { + throw new NullPointerException("remoteAddress"); + } + if (!validatePromise(promise, false)) { + // cancelled + return promise; + } + + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeConnect(remoteAddress, localAddress, promise); + } else { + safeExecute(executor, new OneTimeTask() { + @Override + public void run() { + next.invokeConnect(remoteAddress, localAddress, promise); + } + }, promise, null); + } + + return promise; + } + + private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + try { + ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise); + } catch (Throwable t) { + notifyOutboundHandlerException(t, promise); + } + } + + @Override + public ChannelFuture disconnect(final ChannelPromise promise) { + if (!validatePromise(promise, false)) { + // cancelled + return promise; + } + + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + // Translate disconnect to close if the channel has no notion of disconnect-reconnect. + // So far, UDP/IP is the only transport that has such behavior. + if (!channel().metadata().hasDisconnect()) { + next.invokeClose(promise); + } else { + next.invokeDisconnect(promise); + } + } else { + safeExecute(executor, new OneTimeTask() { + @Override + public void run() { + if (!channel().metadata().hasDisconnect()) { + next.invokeClose(promise); + } else { + next.invokeDisconnect(promise); + } + } + }, promise, null); + } + + return promise; + } + + private void invokeDisconnect(ChannelPromise promise) { + try { + ((ChannelOutboundHandler) handler()).disconnect(this, promise); + } catch (Throwable t) { + notifyOutboundHandlerException(t, promise); + } + } + + @Override + public ChannelFuture close(final ChannelPromise promise) { + if (!validatePromise(promise, false)) { + // cancelled + return promise; + } + + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeClose(promise); + } else { + safeExecute(executor, new OneTimeTask() { + @Override + public void run() { + next.invokeClose(promise); + } + }, promise, null); + } + + return promise; + } + + private void invokeClose(ChannelPromise promise) { + try { + ((ChannelOutboundHandler) handler()).close(this, promise); + } catch (Throwable t) { + notifyOutboundHandlerException(t, promise); + } + } + + @Override + public ChannelFuture deregister(final ChannelPromise promise) { + if (!validatePromise(promise, false)) { + // cancelled + return promise; + } + + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeDeregister(promise); + } else { + safeExecute(executor, new OneTimeTask() { + @Override + public void run() { + next.invokeDeregister(promise); + } + }, promise, null); + } + + return promise; + } + + private void invokeDeregister(ChannelPromise promise) { + try { + ((ChannelOutboundHandler) handler()).deregister(this, promise); + } catch (Throwable t) { + notifyOutboundHandlerException(t, promise); + } + } + + @Override + public ChannelHandlerContext read() { + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeRead(); + } else { + Runnable task = next.invokeReadTask; + if (task == null) { + next.invokeReadTask = task = new Runnable() { + @Override + public void run() { + next.invokeRead(); + } + }; + } + executor.execute(task); + } + + return this; + } + + private void invokeRead() { + try { + ((ChannelOutboundHandler) handler()).read(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelFuture write(Object msg) { + return write(msg, newPromise()); + } + + @Override + public ChannelFuture write(final Object msg, final ChannelPromise promise) { + if (msg == null) { + throw new NullPointerException("msg"); + } + + if (!validatePromise(promise, true)) { + ReferenceCountUtil.release(msg); + // cancelled + return promise; + } + write(msg, false, promise); + + return promise; + } + + private void invokeWrite(Object msg, ChannelPromise promise) { + try { + ((ChannelOutboundHandler) handler()).write(this, msg, promise); + } catch (Throwable t) { + notifyOutboundHandlerException(t, promise); + } + } + + @Override + public ChannelHandlerContext flush() { + final AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeFlush(); + } else { + Runnable task = next.invokeFlushTask; + if (task == null) { + next.invokeFlushTask = task = new Runnable() { + @Override + public void run() { + next.invokeFlush(); + } + }; + } + safeExecute(executor, task, channel.voidPromise(), null); + } + + return this; + } + + private void invokeFlush() { + try { + ((ChannelOutboundHandler) handler()).flush(this); + } catch (Throwable t) { + notifyHandlerException(t); + } + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + if (msg == null) { + throw new NullPointerException("msg"); + } + + if (!validatePromise(promise, true)) { + ReferenceCountUtil.release(msg); + // cancelled + return promise; + } + + write(msg, true, promise); + + return promise; + } + + private void write(Object msg, boolean flush, ChannelPromise promise) { + + AbstractChannelHandlerContext next = findContextOutbound(); + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + next.invokeWrite(msg, promise); + if (flush) { + next.invokeFlush(); + } + } else { + int size = channel.estimatorHandle().size(msg); + if (size > 0) { + ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer(); + // Check for null as it may be set to null if the channel is closed already + if (buffer != null) { + buffer.incrementPendingOutboundBytes(size); + } + } + Runnable task; + if (flush) { + task = WriteAndFlushTask.newInstance(next, msg, size, promise); + } else { + task = WriteTask.newInstance(next, msg, size, promise); + } + safeExecute(executor, task, promise, msg); + } + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return writeAndFlush(msg, newPromise()); + } + + private static void notifyOutboundHandlerException(Throwable cause, ChannelPromise promise) { + // only try to fail the promise if its not a VoidChannelPromise, as + // the VoidChannelPromise would also fire the cause through the pipeline + if (promise instanceof VoidChannelPromise) { + return; + } + + if (!promise.tryFailure(cause)) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to fail the promise because it's done already: {}", promise, cause); + } + } + } + + private void notifyHandlerException(Throwable cause) { + if (inExceptionCaught(cause)) { + if (logger.isWarnEnabled()) { + logger.warn( + "An exception was thrown by a user handler " + + "while handling an exceptionCaught event", cause); + } + return; + } + + invokeExceptionCaught(cause); + } + + private static boolean inExceptionCaught(Throwable cause) { + do { + StackTraceElement[] trace = cause.getStackTrace(); + if (trace != null) { + for (StackTraceElement t : trace) { + if (t == null) { + break; + } + if ("exceptionCaught".equals(t.getMethodName())) { + return true; + } + } + } + + cause = cause.getCause(); + } while (cause != null); + + return false; + } + + @Override + public ChannelPromise newPromise() { + return new DefaultChannelPromise(channel(), executor()); + } + +// @Override +// public ChannelProgressivePromise newProgressivePromise() { +// return new DefaultChannelProgressivePromise(channel(), executor()); +// } + + @Override + public ChannelFuture newSucceededFuture() { + ChannelFuture succeededFuture = this.succeededFuture; + if (succeededFuture == null) { + this.succeededFuture = succeededFuture = new SucceededChannelFuture(channel(), executor()); + } + return succeededFuture; + } + + @Override + public ChannelFuture newFailedFuture(Throwable cause) { + return new FailedChannelFuture(channel(), executor(), cause); + } + + private boolean validatePromise(ChannelPromise promise, boolean allowVoidPromise) { + if (promise == null) { + throw new NullPointerException("promise"); + } + + if (promise.isDone()) { + // Check if the promise was cancelled and if so signal that the processing of the operation + // should not be performed. + // + // See https://github.com/netty/netty/issues/2349 + if (promise.isCancelled()) { + return false; + } + throw new IllegalArgumentException("promise already done: " + promise); + } + + if (promise.channel() != channel()) { + throw new IllegalArgumentException(String.format( + "promise.channel does not match: %s (expected: %s)", promise.channel(), channel())); + } + + if (promise.getClass() == DefaultChannelPromise.class) { + return true; + } + + if (!allowVoidPromise && promise instanceof VoidChannelPromise) { + throw new IllegalArgumentException( + StringUtil.simpleClassName(VoidChannelPromise.class) + " not allowed for this operation"); + } + + if (promise instanceof AbstractChannel.CloseFuture) { + throw new IllegalArgumentException( + StringUtil.simpleClassName(AbstractChannel.CloseFuture.class) + " not allowed in a pipeline"); + } + return true; + } + + private AbstractChannelHandlerContext findContextInbound() { + AbstractChannelHandlerContext ctx = this; + do { + ctx = ctx.next; + } while (!ctx.inbound); + return ctx; + } + + private AbstractChannelHandlerContext findContextOutbound() { + AbstractChannelHandlerContext ctx = this; + do { + ctx = ctx.prev; + } while (!ctx.outbound); + return ctx; + } + + @Override + public ChannelPromise voidPromise() { + return channel.voidPromise(); + } + + void setRemoved() { + removed = true; + } + + @Override + public boolean isRemoved() { + return removed; + } + + private static void safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) { + try { + executor.execute(runnable); + } catch (Throwable cause) { + try { + promise.setFailure(cause); + } finally { + if (msg != null) { + ReferenceCountUtil.release(msg); + } + } + } + } + + abstract static class AbstractWriteTask extends RecyclableMpscLinkedQueueNode implements Runnable { + private AbstractChannelHandlerContext ctx; + private Object msg; + private ChannelPromise promise; + private int size; + + private AbstractWriteTask(Recycler.Handle handle) { + super(handle); + } + + protected static void init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, + Object msg, int size, ChannelPromise promise) { + task.ctx = ctx; + task.msg = msg; + task.promise = promise; + task.size = size; + } + + @Override + public final void run() { + try { + if (size > 0) { + ChannelOutboundBuffer buffer = ctx.channel.unsafe().outboundBuffer(); + // Check for null as it may be set to null if the channel is closed already + if (buffer != null) { + buffer.decrementPendingOutboundBytes(size); + } + } + write(ctx, msg, promise); + } finally { + // Set to null so the GC can collect them directly + ctx = null; + msg = null; + promise = null; + } + } + + @Override + public Runnable value() { + return this; + } + + protected void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) { + ctx.invokeWrite(msg, promise); + } + } + + static final class WriteTask extends AbstractWriteTask implements SingleThreadEventLoop.NonWakeupRunnable { + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected WriteTask newObject(Handle handle) { + return new WriteTask(handle); + } + }; + + private static WriteTask newInstance( + AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) { + WriteTask task = RECYCLER.get(); + init(task, ctx, msg, size, promise); + return task; + } + + private WriteTask(Recycler.Handle handle) { + super(handle); + } + + @Override + protected void recycle(Recycler.Handle handle) { + RECYCLER.recycle(this, handle); + } + } + + static final class WriteAndFlushTask extends AbstractWriteTask { + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected WriteAndFlushTask newObject(Handle handle) { + return new WriteAndFlushTask(handle); + } + }; + + private static WriteAndFlushTask newInstance( + AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) { + WriteAndFlushTask task = RECYCLER.get(); + init(task, ctx, msg, size, promise); + return task; + } + + private WriteAndFlushTask(Recycler.Handle handle) { + super(handle); + } + + @Override + public void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) { + super.write(ctx, msg, promise); + ctx.invokeFlush(); + } + + @Override + protected void recycle(Recycler.Handle handle) { + RECYCLER.recycle(this, handle); + } + } +} diff --git a/common/src/common/net/channel/AbstractServerChannel.java b/common/src/common/net/channel/AbstractServerChannel.java new file mode 100644 index 0000000..ae1bd45 --- /dev/null +++ b/common/src/common/net/channel/AbstractServerChannel.java @@ -0,0 +1,83 @@ +/* + * 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 common.net.channel; + +import java.net.SocketAddress; + +/** + * A skeletal server-side {@link Channel} implementation. A server-side + * {@link Channel} does not allow the following operations: + *
    + *
  • {@link #connect(SocketAddress, ChannelPromise)}
  • + *
  • {@link #disconnect(ChannelPromise)}
  • + *
  • {@link #write(Object, ChannelPromise)}
  • + *
  • {@link #flush()}
  • + *
  • and the shortcut methods which calls the methods mentioned above + *
+ */ +public abstract class AbstractServerChannel extends AbstractChannel implements ServerChannel { + + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + + /** + * Creates a new instance. + */ + protected AbstractServerChannel() { + super(null); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public SocketAddress remoteAddress() { + return null; + } + + @Override + protected SocketAddress remoteAddress0() { + return null; + } + + @Override + protected void doDisconnect() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected AbstractUnsafe newUnsafe() { + return new DefaultServerUnsafe(); + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected final Object filterOutboundMessage(Object msg) { + throw new UnsupportedOperationException(); + } + + private final class DefaultServerUnsafe extends AbstractUnsafe { + @Override + public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + safeSetFailure(promise, new UnsupportedOperationException()); + } + } +} diff --git a/common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java b/common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java new file mode 100644 index 0000000..f1eb52d --- /dev/null +++ b/common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java @@ -0,0 +1,182 @@ +/* + * 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 common.net.channel; + +import java.util.ArrayList; +import java.util.List; + +import common.net.buffer.ByteBuf; +import common.net.buffer.ByteBufAllocator; + +/** + * The {@link RecvByteBufAllocator} that automatically increases and + * decreases the predicted buffer size on feed back. + *

+ * It gradually increases the expected number of readable bytes if the previous + * read fully filled the allocated buffer. It gradually decreases the expected + * number of readable bytes if the read operation was not able to fill a certain + * amount of the allocated buffer two times consecutively. Otherwise, it keeps + * returning the same prediction. + */ +public class AdaptiveRecvByteBufAllocator implements RecvByteBufAllocator { + + static final int DEFAULT_MINIMUM = 64; + static final int DEFAULT_INITIAL = 1024; + static final int DEFAULT_MAXIMUM = 65536; + + private static final int INDEX_INCREMENT = 4; + private static final int INDEX_DECREMENT = 1; + + private static final int[] SIZE_TABLE; + + static { + List sizeTable = new ArrayList(); + for (int i = 16; i < 512; i += 16) { + sizeTable.add(i); + } + + for (int i = 512; i > 0; i <<= 1) { + sizeTable.add(i); + } + + SIZE_TABLE = new int[sizeTable.size()]; + for (int i = 0; i < SIZE_TABLE.length; i ++) { + SIZE_TABLE[i] = sizeTable.get(i); + } + } + + public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator(); + + private static int getSizeTableIndex(final int size) { + for (int low = 0, high = SIZE_TABLE.length - 1;;) { + if (high < low) { + return low; + } + if (high == low) { + return high; + } + + int mid = low + high >>> 1; + int a = SIZE_TABLE[mid]; + int b = SIZE_TABLE[mid + 1]; + if (size > b) { + low = mid + 1; + } else if (size < a) { + high = mid - 1; + } else if (size == a) { + return mid; + } else { + return mid + 1; + } + } + } + + private static final class HandleImpl implements Handle { + private final int minIndex; + private final int maxIndex; + private int index; + private int nextReceiveBufferSize; + private boolean decreaseNow; + + HandleImpl(int minIndex, int maxIndex, int initial) { + this.minIndex = minIndex; + this.maxIndex = maxIndex; + + index = getSizeTableIndex(initial); + nextReceiveBufferSize = SIZE_TABLE[index]; + } + + @Override + public ByteBuf allocate(ByteBufAllocator alloc) { + return alloc.ioBuffer(nextReceiveBufferSize); + } + + @Override + public int guess() { + return nextReceiveBufferSize; + } + + @Override + public void record(int actualReadBytes) { + if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) { + if (decreaseNow) { + index = Math.max(index - INDEX_DECREMENT, minIndex); + nextReceiveBufferSize = SIZE_TABLE[index]; + decreaseNow = false; + } else { + decreaseNow = true; + } + } else if (actualReadBytes >= nextReceiveBufferSize) { + index = Math.min(index + INDEX_INCREMENT, maxIndex); + nextReceiveBufferSize = SIZE_TABLE[index]; + decreaseNow = false; + } + } + } + + private final int minIndex; + private final int maxIndex; + private final int initial; + + /** + * Creates a new predictor with the default parameters. With the default + * parameters, the expected buffer size starts from {@code 1024}, does not + * go down below {@code 64}, and does not go up above {@code 65536}. + */ + private AdaptiveRecvByteBufAllocator() { + this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM); + } + + /** + * Creates a new predictor with the specified parameters. + * + * @param minimum the inclusive lower bound of the expected buffer size + * @param initial the initial buffer size when no feed back was received + * @param maximum the inclusive upper bound of the expected buffer size + */ + public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) { + if (minimum <= 0) { + throw new IllegalArgumentException("minimum: " + minimum); + } + if (initial < minimum) { + throw new IllegalArgumentException("initial: " + initial); + } + if (maximum < initial) { + throw new IllegalArgumentException("maximum: " + maximum); + } + + int minIndex = getSizeTableIndex(minimum); + if (SIZE_TABLE[minIndex] < minimum) { + this.minIndex = minIndex + 1; + } else { + this.minIndex = minIndex; + } + + int maxIndex = getSizeTableIndex(maximum); + if (SIZE_TABLE[maxIndex] > maximum) { + this.maxIndex = maxIndex - 1; + } else { + this.maxIndex = maxIndex; + } + + this.initial = initial; + } + + @Override + public Handle newHandle() { + return new HandleImpl(minIndex, maxIndex, initial); + } +} diff --git a/common/src/common/net/channel/Channel.java b/common/src/common/net/channel/Channel.java new file mode 100644 index 0000000..464bbff --- /dev/null +++ b/common/src/common/net/channel/Channel.java @@ -0,0 +1,511 @@ +/* + * 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 common.net.channel; + +import java.net.SocketAddress; + +import common.net.buffer.ByteBufAllocator; +import common.net.util.AttributeMap; + + +/** + * A nexus to a network socket or a component which is capable of I/O + * operations such as read, write, connect, and bind. + *

+ * A channel provides a user: + *

    + *
  • the current state of the channel (e.g. is it open? is it connected?),
  • + *
  • the {@linkplain ChannelConfig configuration parameters} of the channel (e.g. receive buffer size),
  • + *
  • the I/O operations that the channel supports (e.g. read, write, connect, and bind), and
  • + *
  • the {@link ChannelPipeline} which handles all I/O events and requests + * associated with the channel.
  • + *
+ * + *

All I/O operations are asynchronous.

+ *

+ * All I/O operations in Netty are asynchronous. It means any I/O calls will + * return immediately with no guarantee that the requested I/O operation has + * been completed at the end of the call. Instead, you will be returned with + * a {@link ChannelFuture} instance which will notify you when the requested I/O + * operation has succeeded, failed, or canceled. + * + *

Channels are hierarchical

+ *

+ * A {@link Channel} can have a {@linkplain #parent() parent} depending on + * how it was created. For instance, a {@link SocketChannel}, that was accepted + * by {@link ServerSocketChannel}, will return the {@link ServerSocketChannel} + * as its parent on {@link #parent()}. + *

+ * The semantics of the hierarchical structure depends on the transport + * implementation where the {@link Channel} belongs to. For example, you could + * write a new {@link Channel} implementation that creates the sub-channels that + * share one socket connection, as BEEP and + * SSH do. + * + *

Downcast to access transport-specific operations

+ *

+ * Some transports exposes additional operations that is specific to the + * transport. Down-cast the {@link Channel} to sub-type to invoke such + * operations. For example, with the old I/O datagram transport, multicast + * join / leave operations are provided by {@link DatagramChannel}. + * + *

Release resources

+ *

+ * It is important to call {@link #close()} or {@link #close(ChannelPromise)} to release all + * resources once you are done with the {@link Channel}. This ensures all resources are + * released in a proper way, i.e. filehandles. + */ +public interface Channel extends AttributeMap, Comparable { + + /** + * Return the {@link EventLoop} this {@link Channel} was registered too. + */ + EventLoop eventLoop(); + + /** + * Returns the parent of this channel. + * + * @return the parent channel. + * {@code null} if this channel does not have a parent channel. + */ + Channel parent(); + + /** + * Returns the configuration of this channel. + */ + ChannelConfig config(); + + /** + * Returns {@code true} if the {@link Channel} is open an may get active later + */ + boolean isOpen(); + + /** + * Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}. + */ + boolean isRegistered(); + + /** + * Return {@code true} if the {@link Channel} is active and so connected. + */ + boolean isActive(); + + /** + * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}. + */ + ChannelMetadata metadata(); + + /** + * Returns the local address where this channel is bound to. The returned + * {@link SocketAddress} is supposed to be down-cast into more concrete + * type such as {@link InetSocketAddress} to retrieve the detailed + * information. + * + * @return the local address of this channel. + * {@code null} if this channel is not bound. + */ + SocketAddress localAddress(); + + /** + * Returns the remote address where this channel is connected to. The + * returned {@link SocketAddress} is supposed to be down-cast into more + * concrete type such as {@link InetSocketAddress} to retrieve the detailed + * information. + * + * @return the remote address of this channel. + * {@code null} if this channel is not connected. + * If this channel is not connected but it can receive messages + * from arbitrary remote addresses (e.g. {@link DatagramChannel}, + * use {@link DatagramPacket#recipient()} to determine + * the origination of the received message as this method will + * return {@code null}. + */ + SocketAddress remoteAddress(); + + /** + * Returns the {@link ChannelFuture} which will be notified when this + * channel is closed. This method always returns the same future instance. + */ + ChannelFuture closeFuture(); + + /** + * Returns {@code true} if and only if the I/O thread will perform the + * requested write operation immediately. Any write requests made when + * this method returns {@code false} are queued until the I/O thread is + * ready to process the queued write requests. + */ + boolean isWritable(); + + /** + * Returns an internal-use-only object that provides unsafe operations. + */ + Unsafe unsafe(); + + /** + * Return the assigned {@link ChannelPipeline} + */ + ChannelPipeline pipeline(); + + /** + * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s. + */ + ByteBufAllocator alloc(); + + /** + * Return a new {@link ChannelPromise}. + */ + ChannelPromise newPromise(); + + /** + * Return an new {@link ChannelProgressivePromise} + */ +// ChannelProgressivePromise newProgressivePromise(); + + /** + * Create a new {@link ChannelFuture} which is marked as succeeded already. So {@link ChannelFuture#isSuccess()} + * will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also + * every call of blocking methods will just return without blocking. + */ + ChannelFuture newSucceededFuture(); + + /** + * Create a new {@link ChannelFuture} which is marked as failed already. So {@link ChannelFuture#isSuccess()} + * will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also + * every call of blocking methods will just return without blocking. + */ + ChannelFuture newFailedFuture(Throwable cause); + + /** + * Return a special ChannelPromise which can be reused for different operations. + *

+ * It's only supported to use + * it for {@link Channel#write(Object, ChannelPromise)}. + *

+ *

+ * Be aware that the returned {@link ChannelPromise} will not support most operations and should only be used + * if you want to save an object allocation for every write operation. You will not be able to detect if the + * operation was complete, only if it failed as the implementation will call + * {@link ChannelPipeline#fireExceptionCaught(Throwable)} in this case. + *

+ * Be aware this is an expert feature and should be used with care! + */ + ChannelPromise voidPromise(); + + /** + * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method + * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture bind(SocketAddress localAddress); + + /** + * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + *

+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with + * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException} + * will be used. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress); + + /** + * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress); + + /** + * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture disconnect(); + + /** + * Request to close this {@link Channel} and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of + * an error. + * + * After it is closed it is not possible to reuse it again. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture close(); + + /** + * Request to deregister this {@link Channel} from the previous assigned {@link EventExecutor} and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + * + */ + ChannelFuture deregister(); + + /** + * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method + * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise); + + /** + * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + * + * The given {@link ChannelFuture} will be notified. + * + *

+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with + * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException} + * will be used. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise); + + /** + * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + * + * The given {@link ChannelPromise} will be notified and also returned. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); + + /** + * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture disconnect(ChannelPromise promise); + + /** + * Request to close this {@link Channel} and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of + * an error. + * + * After it is closed it is not possible to reuse it again. + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture close(ChannelPromise promise); + + /** + * Request to deregister this {@link Channel} from the previous assigned {@link EventExecutor} and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture deregister(ChannelPromise promise); + + /** + * Request to Read data from the {@link Channel} into the first inbound buffer, triggers an + * {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was + * read, and triggers a + * {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the + * handler can decide to continue reading. If there's a pending read operation already, this method does nothing. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#read(ChannelHandlerContext)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + Channel read(); + + /** + * Request to write a message via this {@link Channel} through the {@link ChannelPipeline}. + * This method will not request to actual flush, so be sure to call {@link #flush()} + * once you want to request to flush all pending data to the actual transport. + */ + ChannelFuture write(Object msg); + + /** + * Request to write a message via this {@link Channel} through the {@link ChannelPipeline}. + * This method will not request to actual flush, so be sure to call {@link #flush()} + * once you want to request to flush all pending data to the actual transport. + */ + ChannelFuture write(Object msg, ChannelPromise promise); + + /** + * Request to flush all pending messages. + */ + Channel flush(); + + /** + * Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}. + */ + ChannelFuture writeAndFlush(Object msg, ChannelPromise promise); + + /** + * Shortcut for call {@link #write(Object)} and {@link #flush()}. + */ + ChannelFuture writeAndFlush(Object msg); + + /** + * Unsafe operations that should never be called from user-code. These methods + * are only provided to implement the actual transport, and must be invoked from an I/O thread except for the + * following methods: + *

    + *
  • {@link #localAddress()}
  • + *
  • {@link #remoteAddress()}
  • + *
  • {@link #closeForcibly()}
  • + *
  • {@link #register(EventLoop, ChannelPromise)}
  • + *
  • {@link #voidPromise()}
  • + *
+ */ + interface Unsafe { + /** + * Return the {@link SocketAddress} to which is bound local or + * {@code null} if none. + */ + SocketAddress localAddress(); + + /** + * Return the {@link SocketAddress} to which is bound remote or + * {@code null} if none is bound yet. + */ + SocketAddress remoteAddress(); + + /** + * Register the {@link Channel} of the {@link ChannelPromise} with the {@link EventLoop} and notify + * the {@link ChannelFuture} once the registration was complete. + */ + void register(EventLoop eventLoop, ChannelPromise promise); + + /** + * Bind the {@link SocketAddress} to the {@link Channel} of the {@link ChannelPromise} and notify + * it once its done. + */ + void bind(SocketAddress localAddress, ChannelPromise promise); + + /** + * Connect the {@link Channel} of the given {@link ChannelFuture} with the given remote {@link SocketAddress}. + * If a specific local {@link SocketAddress} should be used it need to be given as argument. Otherwise just + * pass {@code null} to it. + * + * The {@link ChannelPromise} will get notified once the connect operation was complete. + */ + void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); + + /** + * Disconnect the {@link Channel} of the {@link ChannelFuture} and notify the {@link ChannelPromise} once the + * operation was complete. + */ + void disconnect(ChannelPromise promise); + + /** + * Close the {@link Channel} of the {@link ChannelPromise} and notify the {@link ChannelPromise} once the + * operation was complete. + */ + void close(ChannelPromise promise); + + /** + * Closes the {@link Channel} immediately without firing any events. Probably only useful + * when registration attempt failed. + */ + void closeForcibly(); + + /** + * Deregister the {@link Channel} of the {@link ChannelPromise} from {@link EventLoop} and notify the + * {@link ChannelPromise} once the operation was complete. + */ + void deregister(ChannelPromise promise); + + /** + * Schedules a read operation that fills the inbound buffer of the first {@link ChannelInboundHandler} in the + * {@link ChannelPipeline}. If there's already a pending read operation, this method does nothing. + */ + void beginRead(); + + /** + * Schedules a write operation. + */ + void write(Object msg, ChannelPromise promise); + + /** + * Flush out all write operations scheduled via {@link #write(Object, ChannelPromise)}. + */ + void flush(); + + /** + * Return a special ChannelPromise which can be reused and passed to the operations in {@link Unsafe}. + * It will never be notified of a success or error and so is only a placeholder for operations + * that take a {@link ChannelPromise} as argument but for which you not want to get notified. + */ + ChannelPromise voidPromise(); + + /** + * Returns the {@link ChannelOutboundBuffer} of the {@link Channel} where the pending write requests are stored. + */ + ChannelOutboundBuffer outboundBuffer(); + } +} diff --git a/common/src/common/net/channel/ChannelConfig.java b/common/src/common/net/channel/ChannelConfig.java new file mode 100644 index 0000000..9204cd1 --- /dev/null +++ b/common/src/common/net/channel/ChannelConfig.java @@ -0,0 +1,249 @@ +/* + * 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 common.net.channel; + +import java.util.Map; + +import common.net.buffer.ByteBufAllocator; + +/** + * A set of configuration properties of a {@link Channel}. + *

+ * Please down-cast to more specific configuration type such as + * {@link SocketChannelConfig} or use {@link #setOptions(Map)} to set the + * transport-specific properties: + *

+ * {@link Channel} ch = ...;
+ * {@link SocketChannelConfig} cfg = ({@link SocketChannelConfig}) ch.getConfig();
+ * cfg.setTcpNoDelay(false);
+ * 
+ * + *

Option map

+ * + * An option map property is a dynamic write-only property which allows + * the configuration of a {@link Channel} without down-casting its associated + * {@link ChannelConfig}. To update an option map, please call {@link #setOptions(Map)}. + *

+ * All {@link ChannelConfig} has the following options: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameAssociated setter method
{@link ChannelOption#CONNECT_TIMEOUT_MILLIS}{@link #setConnectTimeoutMillis(int)}
{@link ChannelOption#WRITE_SPIN_COUNT}{@link #setWriteSpinCount(int)}
{@link ChannelOption#ALLOCATOR}{@link #setAllocator(ByteBufAllocator)}
{@link ChannelOption#AUTO_READ}{@link #setAutoRead(boolean)}
+ *

+ * More options are available in the sub-types of {@link ChannelConfig}. For + * example, you can configure the parameters which are specific to a TCP/IP + * socket as explained in {@link SocketChannelConfig}. + */ +public interface ChannelConfig { + + /** + * Return all set {@link ChannelOption}'s. + */ + Map, Object> getOptions(); + + /** + * Sets the configuration properties from the specified {@link Map}. + */ + boolean setOptions(Map, ?> options); + + /** + * Return the value of the given {@link ChannelOption} + */ + T getOption(ChannelOption option); + + /** + * Sets a configuration property with the specified name and value. + * To override this method properly, you must call the super class: + *

+     * public boolean setOption(ChannelOption<T> option, T value) {
+     *     if (super.setOption(option, value)) {
+     *         return true;
+     *     }
+     *
+     *     if (option.equals(additionalOption)) {
+     *         ....
+     *         return true;
+     *     }
+     *
+     *     return false;
+     * }
+     * 
+ * + * @return {@code true} if and only if the property has been set + */ + boolean setOption(ChannelOption option, T value); + + /** + * Returns the connect timeout of the channel in milliseconds. If the + * {@link Channel} does not support connect operation, this property is not + * used at all, and therefore will be ignored. + * + * @return the connect timeout in milliseconds. {@code 0} if disabled. + */ + int getConnectTimeoutMillis(); + + /** + * Sets the connect timeout of the channel in milliseconds. If the + * {@link Channel} does not support connect operation, this property is not + * used at all, and therefore will be ignored. + * + * @param connectTimeoutMillis the connect timeout in milliseconds. + * {@code 0} to disable. + */ + ChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); + + /** + * Returns the maximum number of messages to read per read loop. + * a {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object) channelRead()} event. + * If this value is greater than 1, an event loop might attempt to read multiple times to procure multiple messages. + */ + int getMaxMessagesPerRead(); + + /** + * Sets the maximum number of messages to read per read loop. + * If this value is greater than 1, an event loop might attempt to read multiple times to procure multiple messages. + */ + ChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); + + /** + * Returns the maximum loop count for a write operation until + * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. + * It is similar to what a spin lock is used for in concurrency programming. + * It improves memory utilization and write throughput depending on + * the platform that JVM runs on. The default value is {@code 16}. + */ + int getWriteSpinCount(); + + /** + * Sets the maximum loop count for a write operation until + * {@link WritableByteChannel#write(ByteBuffer)} returns a non-zero value. + * It is similar to what a spin lock is used for in concurrency programming. + * It improves memory utilization and write throughput depending on + * the platform that JVM runs on. The default value is {@code 16}. + * + * @throws IllegalArgumentException + * if the specified value is {@code 0} or less than {@code 0} + */ + ChannelConfig setWriteSpinCount(int writeSpinCount); + + /** + * Returns {@link ByteBufAllocator} which is used for the channel + * to allocate buffers. + */ + ByteBufAllocator getAllocator(); + + /** + * Set the {@link ByteBufAllocator} which is used for the channel + * to allocate buffers. + */ + ChannelConfig setAllocator(ByteBufAllocator allocator); + + /** + * Returns {@link RecvByteBufAllocator} which is used for the channel + * to allocate receive buffers. + */ + RecvByteBufAllocator getRecvByteBufAllocator(); + + /** + * Set the {@link ByteBufAllocator} which is used for the channel + * to allocate receive buffers. + */ + ChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); + + /** + * Returns {@code true} if and only if {@link ChannelHandlerContext#read()} will be invoked automatically so that + * a user application doesn't need to call it at all. The default value is {@code true}. + */ + boolean isAutoRead(); + + /** + * Sets if {@link ChannelHandlerContext#read()} will be invoked automatically so that a user application doesn't + * need to call it at all. The default value is {@code true}. + */ + ChannelConfig setAutoRead(boolean autoRead); + + /** + * @deprecated From version 5.0, {@link Channel} will not be closed on write failure. + * + * Returns {@code true} if and only if the {@link Channel} will be closed automatically and immediately on + * write failure. The default is {@code false}. + */ + @Deprecated + boolean isAutoClose(); + + /** + * @deprecated From version 5.0, {@link Channel} will not be closed on write failure. + * + * Sets whether the {@link Channel} should be closed automatically and immediately on write faillure. + * The default is {@code false}. + */ + @Deprecated + ChannelConfig setAutoClose(boolean autoClose); + + /** + * Returns the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code false}. + */ + int getWriteBufferHighWaterMark(); + + /** + * Sets the high water mark of the write buffer. If the number of bytes + * queued in the write buffer exceeds this value, {@link Channel#isWritable()} + * will start to return {@code false}. + */ + ChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark); + + /** + * Returns the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will start to return + * {@code true} again. + */ + int getWriteBufferLowWaterMark(); + + /** + * Sets the low water mark of the write buffer. Once the number of bytes + * queued in the write buffer exceeded the + * {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then + * dropped down below this value, {@link Channel#isWritable()} will start to return + * {@code true} again. + */ + ChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark); + + /** + * Returns {@link MessageSizeEstimator} which is used for the channel + * to detect the size of a message. + */ + MessageSizeEstimator getMessageSizeEstimator(); + + /** + * Set the {@link ByteBufAllocator} which is used for the channel + * to detect the size of a message. + */ + ChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator); +} diff --git a/common/src/common/net/channel/ChannelException.java b/common/src/common/net/channel/ChannelException.java new file mode 100644 index 0000000..721d03f --- /dev/null +++ b/common/src/common/net/channel/ChannelException.java @@ -0,0 +1,51 @@ +/* + * 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 common.net.channel; + +/** + * A {@link RuntimeException} which is thrown when an I/O operation fails. + */ +public class ChannelException extends RuntimeException { + + private static final long serialVersionUID = 2908618315971075004L; + + /** + * Creates a new exception. + */ + public ChannelException() { + } + + /** + * Creates a new exception. + */ + public ChannelException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new exception. + */ + public ChannelException(String message) { + super(message); + } + + /** + * Creates a new exception. + */ + public ChannelException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/channel/ChannelFuture.java b/common/src/common/net/channel/ChannelFuture.java new file mode 100644 index 0000000..e849ad5 --- /dev/null +++ b/common/src/common/net/channel/ChannelFuture.java @@ -0,0 +1,192 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; + + +/** + * The result of an asynchronous {@link Channel} I/O operation. + *

+ * All I/O operations in Netty are asynchronous. It means any I/O calls will + * return immediately with no guarantee that the requested I/O operation has + * been completed at the end of the call. Instead, you will be returned with + * a {@link ChannelFuture} instance which gives you the information about the + * result or status of the I/O operation. + *

+ * A {@link ChannelFuture} is either uncompleted or completed. + * When an I/O operation begins, a new future object is created. The new future + * is uncompleted initially - it is neither succeeded, failed, nor cancelled + * because the I/O operation is not finished yet. If the I/O operation is + * finished either successfully, with failure, or by cancellation, the future is + * marked as completed with more specific information, such as the cause of the + * failure. Please note that even failure and cancellation belong to the + * completed state. + *

+ *                                      +---------------------------+
+ *                                      | Completed successfully    |
+ *                                      +---------------------------+
+ *                                 +---->      isDone() = true      |
+ * +--------------------------+    |    |   isSuccess() = true      |
+ * |        Uncompleted       |    |    +===========================+
+ * +--------------------------+    |    | Completed with failure    |
+ * |      isDone() = false    |    |    +---------------------------+
+ * |   isSuccess() = false    |----+---->   isDone() = true         |
+ * | isCancelled() = false    |    |    | cause() = non-null     |
+ * |    cause() = null     |    |    +===========================+
+ * +--------------------------+    |    | Completed by cancellation |
+ *                                 |    +---------------------------+
+ *                                 +---->      isDone() = true      |
+ *                                      | isCancelled() = true      |
+ *                                      +---------------------------+
+ * 
+ * + * Various methods are provided to let you check if the I/O operation has been + * completed, wait for the completion, and retrieve the result of the I/O + * operation. It also allows you to add {@link ChannelFutureListener}s so you + * can get notified when the I/O operation is completed. + * + *

Prefer {@link #addListener(GenericFutureListener)} to {@link #await()}

+ * + * It is recommended to prefer {@link #addListener(GenericFutureListener)} to + * {@link #await()} wherever possible to get notified when an I/O operation is + * done and to do any follow-up tasks. + *

+ * {@link #addListener(GenericFutureListener)} is non-blocking. It simply adds + * the specified {@link ChannelFutureListener} to the {@link ChannelFuture}, and + * I/O thread will notify the listeners when the I/O operation associated with + * the future is done. {@link ChannelFutureListener} yields the best + * performance and resource utilization because it does not block at all, but + * it could be tricky to implement a sequential logic if you are not used to + * event-driven programming. + *

+ * By contrast, {@link #await()} is a blocking operation. Once called, the + * caller thread blocks until the operation is done. It is easier to implement + * a sequential logic with {@link #await()}, but the caller thread blocks + * unnecessarily until the I/O operation is done and there's relatively + * expensive cost of inter-thread notification. Moreover, there's a chance of + * dead lock in a particular circumstance, which is described below. + * + *

Do not call {@link #await()} inside {@link ChannelHandler}

+ *

+ * The event handler methods in {@link ChannelHandler} is usually called by + * an I/O thread. If {@link #await()} is called by an event handler + * method, which is called by the I/O thread, the I/O operation it is waiting + * for might never be complete because {@link #await()} can block the I/O + * operation it is waiting for, which is a dead lock. + *

+ * // BAD - NEVER DO THIS
+ * {@code @Override}
+ * public void channelRead({@link ChannelHandlerContext} ctx, GoodByeMessage msg) {
+ *     {@link ChannelFuture} future = ctx.channel().close();
+ *     future.awaitUninterruptibly();
+ *     // Perform post-closure operation
+ *     // ...
+ * }
+ *
+ * // GOOD
+ * {@code @Override}
+ * public void channelRead({@link ChannelHandlerContext} ctx,  GoodByeMessage msg) {
+ *     {@link ChannelFuture} future = ctx.channel().close();
+ *     future.addListener(new {@link ChannelFutureListener}() {
+ *         public void operationComplete({@link ChannelFuture} future) {
+ *             // Perform post-closure operation
+ *             // ...
+ *         }
+ *     });
+ * }
+ * 
+ *

+ * In spite of the disadvantages mentioned above, there are certainly the cases + * where it is more convenient to call {@link #await()}. In such a case, please + * make sure you do not call {@link #await()} in an I/O thread. Otherwise, + * {@link BlockingOperationException} will be raised to prevent a dead lock. + * + *

Do not confuse I/O timeout and await timeout

+ * + * The timeout value you specify with {@link #await(long)}, + * {@link #await(long, TimeUnit)}, {@link #awaitUninterruptibly(long)}, or + * {@link #awaitUninterruptibly(long, TimeUnit)} are not related with I/O + * timeout at all. If an I/O operation times out, the future will be marked as + * 'completed with failure,' as depicted in the diagram above. For example, + * connect timeout should be configured via a transport-specific option: + *
+ * // BAD - NEVER DO THIS
+ * {@link Bootstrap} b = ...;
+ * {@link ChannelFuture} f = b.connect(...);
+ * f.awaitUninterruptibly(10, TimeUnit.SECONDS);
+ * if (f.isCancelled()) {
+ *     // Connection attempt cancelled by user
+ * } else if (!f.isSuccess()) {
+ *     // You might get a NullPointerException here because the future
+ *     // might not be completed yet.
+ *     f.cause().printStackTrace();
+ * } else {
+ *     // Connection established successfully
+ * }
+ *
+ * // GOOD
+ * {@link Bootstrap} b = ...;
+ * // Configure the connect timeout option.
+ * b.option({@link ChannelOption}.CONNECT_TIMEOUT_MILLIS, 10000);
+ * {@link ChannelFuture} f = b.connect(...);
+ * f.awaitUninterruptibly();
+ *
+ * // Now we are sure the future is completed.
+ * assert f.isDone();
+ *
+ * if (f.isCancelled()) {
+ *     // Connection attempt cancelled by user
+ * } else if (!f.isSuccess()) {
+ *     f.cause().printStackTrace();
+ * } else {
+ *     // Connection established successfully
+ * }
+ * 
+ */ +public interface ChannelFuture extends Future { + + /** + * Returns a channel where the I/O operation associated with this + * future takes place. + */ + Channel channel(); + + @Override + ChannelFuture addListener(GenericFutureListener> listener); + + @Override + ChannelFuture addListeners(GenericFutureListener>... listeners); + + @Override + ChannelFuture removeListener(GenericFutureListener> listener); + + @Override + ChannelFuture removeListeners(GenericFutureListener>... listeners); + + @Override + ChannelFuture sync() throws InterruptedException; + + @Override + ChannelFuture syncUninterruptibly(); + + @Override + ChannelFuture await() throws InterruptedException; + + @Override + ChannelFuture awaitUninterruptibly(); +} diff --git a/common/src/common/net/channel/ChannelFutureListener.java b/common/src/common/net/channel/ChannelFutureListener.java new file mode 100644 index 0000000..ec088b6 --- /dev/null +++ b/common/src/common/net/channel/ChannelFutureListener.java @@ -0,0 +1,74 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.GenericFutureListener; + + +/** + * Listens to the result of a {@link ChannelFuture}. The result of the + * asynchronous {@link Channel} I/O operation is notified once this listener + * is added by calling {@link ChannelFuture#addListener(GenericFutureListener)}. + * + *

Return the control to the caller quickly

+ * + * {@link #operationComplete(Future)} is directly called by an I/O + * thread. Therefore, performing a time consuming task or a blocking operation + * in the handler method can cause an unexpected pause during I/O. If you need + * to perform a blocking operation on I/O completion, try to execute the + * operation in a different thread using a thread pool. + */ +public interface ChannelFutureListener extends GenericFutureListener { + + /** + * A {@link ChannelFutureListener} that closes the {@link Channel} which is + * associated with the specified {@link ChannelFuture}. + */ + ChannelFutureListener CLOSE = new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + future.channel().close(); + } + }; + + /** + * A {@link ChannelFutureListener} that closes the {@link Channel} when the + * operation ended up with a failure or cancellation rather than a success. + */ + ChannelFutureListener CLOSE_ON_FAILURE = new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (!future.isSuccess()) { + future.channel().close(); + } + } + }; + + /** + * A {@link ChannelFutureListener} that forwards the {@link Throwable} of the {@link ChannelFuture} into the + * {@link ChannelPipeline}. This mimics the old behavior of Netty 3. + */ + ChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (!future.isSuccess()) { + future.channel().pipeline().fireExceptionCaught(future.cause()); + } + } + }; + + // Just a type alias +} diff --git a/common/src/common/net/channel/ChannelHandler.java b/common/src/common/net/channel/ChannelHandler.java new file mode 100644 index 0000000..4b78cde --- /dev/null +++ b/common/src/common/net/channel/ChannelHandler.java @@ -0,0 +1,211 @@ +/* + * 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 common.net.channel; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Handles or intercepts a {@link ChannelInboundInvoker} or {@link ChannelOutboundInvoker} operation, and forwards it + * to the next handler in a {@link ChannelPipeline}. + * + *

Sub-types

+ *

+ * {@link ChannelHandler} itself does not provide many methods. To handle a + * a {@link ChannelInboundInvoker} or {@link ChannelOutboundInvoker} operation + * you need to implement its sub-interfaces. There are many different sub-interfaces + * which handles inbound and outbound operations. + * + * But the most useful for developers may be: + *

    + *
  • {@link ChannelInboundHandlerAdapter} handles and intercepts inbound operations
  • + *
  • {@link ChannelOutboundHandlerAdapter} handles and intercepts outbound operations
  • + *
+ * + * You will also find more detailed explanation from the documentation of + * each sub-interface on how an event is interpreted when it goes upstream and + * downstream respectively. + * + *

The context object

+ *

+ * A {@link ChannelHandler} is provided with a {@link ChannelHandlerContext} + * object. A {@link ChannelHandler} is supposed to interact with the + * {@link ChannelPipeline} it belongs to via a context object. Using the + * context object, the {@link ChannelHandler} can pass events upstream or + * downstream, modify the pipeline dynamically, or store the information + * (using {@link AttributeKey}s) which is specific to the handler. + * + *

State management

+ * + * A {@link ChannelHandler} often needs to store some stateful information. + * The simplest and recommended approach is to use member variables: + *
+ * public interface Message {
+ *     // your methods here
+ * }
+ *
+ * public class DataServerHandler extends {@link SimpleChannelInboundHandler}<Message> {
+ *
+ *     private boolean loggedIn;
+ *
+ *     {@code @Override}
+ *     public void channelRead0({@link ChannelHandlerContext} ctx, Message message) {
+ *         {@link Channel} ch = e.getChannel();
+ *         if (message instanceof LoginMessage) {
+ *             authenticate((LoginMessage) message);
+ *             loggedIn = true;
+ *         } else (message instanceof GetDataMessage) {
+ *             if (loggedIn) {
+ *                 ch.write(fetchSecret((GetDataMessage) message));
+ *             } else {
+ *                 fail();
+ *             }
+ *         }
+ *     }
+ *     ...
+ * }
+ * 
+ * Because the handler instance has a state variable which is dedicated to + * one connection, you have to create a new handler instance for each new + * channel to avoid a race condition where a unauthenticated client can get + * the confidential information: + *
+ * // Create a new handler instance per channel.
+ * // See {@link ChannelInitializer#initChannel(Channel)}.
+ * public class DataServerInitializer extends {@link ChannelInitializer}<{@link Channel}> {
+ *     {@code @Override}
+ *     public void initChannel({@link Channel} channel) {
+ *         channel.pipeline().addLast("handler", new DataServerHandler());
+ *     }
+ * }
+ *
+ * 
+ * + *

Using {@link AttributeKey}

+ * + * Although it's recommended to use member variables to store the state of a + * handler, for some reason you might not want to create many handler instances. + * In such a case, you can use {@link AttributeKey}s which is provided by + * {@link ChannelHandlerContext}: + *
+ * public interface Message {
+ *     // your methods here
+ * }
+ *
+ * {@code @Sharable}
+ * public class DataServerHandler extends {@link SimpleChannelInboundHandler}<Message> {
+ *     private final {@link AttributeKey}<{@link Boolean}> auth =
+ *           {@link AttributeKey#valueOf(String) AttributeKey.valueOf("auth")};
+ *
+ *     {@code @Override}
+ *     public void channelRead({@link ChannelHandlerContext} ctx, Message message) {
+ *         {@link Attribute}<{@link Boolean}> attr = ctx.attr(auth);
+ *         {@link Channel} ch = ctx.channel();
+ *         if (message instanceof LoginMessage) {
+ *             authenticate((LoginMessage) o);
+ *             attr.set(true);
+ *         } else (message instanceof GetDataMessage) {
+ *             if (Boolean.TRUE.equals(attr.get())) {
+ *                 ch.write(fetchSecret((GetDataMessage) o));
+ *             } else {
+ *                 fail();
+ *             }
+ *         }
+ *     }
+ *     ...
+ * }
+ * 
+ * Now that the state of the handler isattached to the {@link ChannelHandlerContext}, you can add the + * same handler instance to different pipelines: + *
+ * public class DataServerInitializer extends {@link ChannelInitializer}<{@link Channel}> {
+ *
+ *     private static final DataServerHandler SHARED = new DataServerHandler();
+ *
+ *     {@code @Override}
+ *     public void initChannel({@link Channel} channel) {
+ *         channel.pipeline().addLast("handler", SHARED);
+ *     }
+ * }
+ * 
+ * + * + *

The {@code @Sharable} annotation

+ *

+ * In the example above which used an {@link AttributeKey}, + * you might have noticed the {@code @Sharable} annotation. + *

+ * If a {@link ChannelHandler} is annotated with the {@code @Sharable} + * annotation, it means you can create an instance of the handler just once and + * add it to one or more {@link ChannelPipeline}s multiple times without + * a race condition. + *

+ * If this annotation is not specified, you have to create a new handler + * instance every time you add it to a pipeline because it has unshared state + * such as member variables. + *

+ * This annotation is provided for documentation purpose, just like + * the JCIP annotations. + * + *

Additional resources worth reading

+ *

+ * Please refer to the {@link ChannelHandler}, and + * {@link ChannelPipeline} to find out more about inbound and outbound operations, + * what fundamental differences they have, how they flow in a pipeline, and how to handle + * the operation in your application. + */ +public interface ChannelHandler { + + /** + * Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events. + */ + void handlerAdded(ChannelHandlerContext ctx) throws Exception; + + /** + * Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events + * anymore. + */ + void handlerRemoved(ChannelHandlerContext ctx) throws Exception; + + /** + * Gets called if a {@link Throwable} was thrown. + */ + void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; + + /** + * Indicates that the same instance of the annotated {@link ChannelHandler} + * can be added to one or more {@link ChannelPipeline}s multiple times + * without a race condition. + *

+ * If this annotation is not specified, you have to create a new handler + * instance every time you add it to a pipeline because it has unshared + * state such as member variables. + *

+ * This annotation is provided for documentation purpose, just like + * the JCIP annotations. + */ + @Inherited + @Documented + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface Sharable { + // no value + } +} diff --git a/common/src/common/net/channel/ChannelHandlerAdapter.java b/common/src/common/net/channel/ChannelHandlerAdapter.java new file mode 100644 index 0000000..7137333 --- /dev/null +++ b/common/src/common/net/channel/ChannelHandlerAdapter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013 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 common.net.channel; + +import java.util.Map; + +import common.net.util.internal.InternalThreadLocalMap; + +/** + * Skelton implementation of a {@link ChannelHandler}. + */ +public abstract class ChannelHandlerAdapter implements ChannelHandler { + + // Not using volatile because it's used only for a sanity check. + boolean added; + + /** + * Return {@code true} if the implementation is {@link Sharable} and so can be added + * to different {@link ChannelPipeline}s. + */ + public boolean isSharable() { + /** + * Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a + * {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different + * {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of + * {@link Thread}s are quite limited anyway. + * + * See #2289. + */ + Class clazz = getClass(); + Map, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache(); + Boolean sharable = cache.get(clazz); + if (sharable == null) { + sharable = clazz.isAnnotationPresent(Sharable.class); + cache.put(clazz, sharable); + } + return sharable; + } + + /** + * Do nothing by default, sub-classes may override this method. + */ + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + // NOOP + } + + /** + * Do nothing by default, sub-classes may override this method. + */ + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + // NOOP + } + + /** + * Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward + * to the next {@link ChannelHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.fireExceptionCaught(cause); + } +} diff --git a/common/src/common/net/channel/ChannelHandlerContext.java b/common/src/common/net/channel/ChannelHandlerContext.java new file mode 100644 index 0000000..81da8c7 --- /dev/null +++ b/common/src/common/net/channel/ChannelHandlerContext.java @@ -0,0 +1,489 @@ +/* + * 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 common.net.channel; + + +import java.net.SocketAddress; + +import common.net.buffer.ByteBufAllocator; +import common.net.util.AttributeMap; +import common.net.util.concurrent.EventExecutor; + +/** + * Enables a {@link ChannelHandler} to interact with its {@link ChannelPipeline} + * and other handlers. A handler can notify the next {@link ChannelHandler} in the {@link ChannelPipeline}, + * modify the {@link ChannelPipeline} it belongs to dynamically. + * + *

Notify

+ * + * You can notify the closest handler in the + * same {@link ChannelPipeline} by calling one of the various methods provided here. + * Please refer to {@link ChannelPipeline} to understand how an event flows. + * + *

Modifying a pipeline

+ * + * You can get the {@link ChannelPipeline} your handler belongs to by calling + * {@link #pipeline()}. A non-trivial application could insert, remove, or + * replace handlers in the pipeline dynamically at runtime. + * + *

Retrieving for later use

+ * + * You can keep the {@link ChannelHandlerContext} for later use, such as + * triggering an event outside the handler methods, even from a different thread. + *
+ * public class MyHandler extends {@link ChannelDuplexHandler} {
+ *
+ *     private {@link ChannelHandlerContext} ctx;
+ *
+ *     public void beforeAdd({@link ChannelHandlerContext} ctx) {
+ *         this.ctx = ctx;
+ *     }
+ *
+ *     public void login(String username, password) {
+ *         ctx.write(new LoginMessage(username, password));
+ *     }
+ *     ...
+ * }
+ * 
+ * + *

Storing stateful information

+ * + * {@link #attr(AttributeKey)} allow you to + * store and access stateful information that is related with a handler and its + * context. Please refer to {@link ChannelHandler} to learn various recommended + * ways to manage stateful information. + * + *

A handler can have more than one context

+ * + * Please note that a {@link ChannelHandler} instance can be added to more than + * one {@link ChannelPipeline}. It means a single {@link ChannelHandler} + * instance can have more than one {@link ChannelHandlerContext} and therefore + * the single instance can be invoked with different + * {@link ChannelHandlerContext}s if it is added to one or more + * {@link ChannelPipeline}s more than once. + *

+ * For example, the following handler will have as many independent {@link AttributeKey}s + * as how many times it is added to pipelines, regardless if it is added to the + * same pipeline multiple times or added to different pipelines multiple times: + *

+ * public class FactorialHandler extends {@link ChannelInboundHandlerAdapter}<{@link Integer}> {
+ *
+ *   private final {@link AttributeKey}<{@link Integer}> counter =
+ *           new {@link AttributeKey}<{@link Integer}>("counter");
+ *
+ *   // This handler will receive a sequence of increasing integers starting
+ *   // from 1.
+ *   {@code @Override}
+ *   public void channelRead({@link ChannelHandlerContext} ctx, {@link Integer} integer) {
+ *     {@link Attribute}<{@link Integer}> attr = ctx.getAttr(counter);
+ *     Integer a = ctx.getAttr(counter).get();
+ *
+ *     if (a == null) {
+ *       a = 1;
+ *     }
+ *
+ *     attr.set(a * integer));
+ *   }
+ * }
+ *
+ * // Different context objects are given to "f1", "f2", "f3", and "f4" even if
+ * // they refer to the same handler instance.  Because the FactorialHandler
+ * // stores its state in a context object (as an (using an {@link AttributeKey}), the factorial is
+ * // calculated correctly 4 times once the two pipelines (p1 and p2) are active.
+ * FactorialHandler fh = new FactorialHandler();
+ *
+ * {@link ChannelPipeline} p1 = {@link Channels}.pipeline();
+ * p1.addLast("f1", fh);
+ * p1.addLast("f2", fh);
+ *
+ * {@link ChannelPipeline} p2 = {@link Channels}.pipeline();
+ * p2.addLast("f3", fh);
+ * p2.addLast("f4", fh);
+ * 
+ * + *

Additional resources worth reading

+ *

+ * Please refer to the {@link ChannelHandler}, and + * {@link ChannelPipeline} to find out more about inbound and outbound operations, + * what fundamental differences they have, how they flow in a pipeline, and how to handle + * the operation in your application. + */ +public interface ChannelHandlerContext + extends AttributeMap { + + /** + * Return the {@link Channel} which is bound to the {@link ChannelHandlerContext}. + */ + Channel channel(); + + /** + * The {@link EventExecutor} that is used to dispatch the events. This can also be used to directly + * submit tasks that get executed in the event loop. For more information please refer to the + * {@link EventExecutor} javadoc. + */ + EventExecutor executor(); + + /** + * The unique name of the {@link ChannelHandlerContext}.The name was used when then {@link ChannelHandler} + * was added to the {@link ChannelPipeline}. This name can also be used to access the registered + * {@link ChannelHandler} from the {@link ChannelPipeline}. + */ + String name(); + + /** + * The {@link ChannelHandler} that is bound this {@link ChannelHandlerContext}. + */ + ChannelHandler handler(); + + /** + * Return {@code true} if the {@link ChannelHandler} which belongs to this {@link ChannelHandler} was removed + * from the {@link ChannelPipeline}. Note that this method is only meant to be called from with in the + * {@link EventLoop}. + */ + boolean isRemoved(); + + /** + * A {@link Channel} was registered to its {@link EventLoop}. + * + * This will result in having the {@link ChannelInboundHandler#channelRegistered(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireChannelRegistered(); + + /** + * A {@link Channel} was unregistered from its {@link EventLoop}. + * + * This will result in having the {@link ChannelInboundHandler#channelUnregistered(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireChannelUnregistered(); + + /** + * A {@link Channel} is active now, which means it is connected. + * + * This will result in having the {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireChannelActive(); + + /** + * A {@link Channel} is inactive now, which means it is closed. + * + * This will result in having the {@link ChannelInboundHandler#channelInactive(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireChannelInactive(); + + /** + * A {@link Channel} received an {@link Throwable} in one of its inbound operations. + * + * This will result in having the {@link ChannelInboundHandler#exceptionCaught(ChannelHandlerContext, Throwable)} + * method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireExceptionCaught(Throwable cause); + + /** + * A {@link Channel} received an user defined event. + * + * This will result in having the {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} + * method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireUserEventTriggered(Object event); + + /** + * A {@link Channel} received a message. + * + * This will result in having the {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} + * method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext fireChannelRead(Object msg); + + /** + * Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)} + * event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + */ + ChannelHandlerContext fireChannelReadComplete(); + + /** + * Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)} + * event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + */ + ChannelHandlerContext fireChannelWritabilityChanged(); + + /** + * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method + * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture bind(SocketAddress localAddress); + + /** + * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + *

+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with + * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException} + * will be used. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress); + + /** + * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress); + + /** + * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture disconnect(); + + /** + * Request to close the {@link Channel} and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of + * an error. + * + * After it is closed it is not possible to reuse it again. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture close(); + + /** + * Request to deregister from the previous assigned {@link EventExecutor} and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + * + */ + ChannelFuture deregister(); + + /** + * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method + * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise); + + /** + * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + * + * The given {@link ChannelFuture} will be notified. + * + *

+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with + * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException} + * will be used. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise); + + /** + * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + * + * The given {@link ChannelPromise} will be notified and also returned. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); + + /** + * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture disconnect(ChannelPromise promise); + + /** + * Request to close the {@link Channel} and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of + * an error. + * + * After it is closed it is not possible to reuse it again. + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture close(ChannelPromise promise); + + /** + * Request to deregister from the previous assigned {@link EventExecutor} and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture deregister(ChannelPromise promise); + + /** + * Request to Read data from the {@link Channel} into the first inbound buffer, triggers an + * {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was + * read, and triggers a + * {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the + * handler can decide to continue reading. If there's a pending read operation already, this method does nothing. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#read(ChannelHandlerContext)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelHandlerContext read(); + + /** + * Request to write a message via this {@link ChannelHandlerContext} through the {@link ChannelPipeline}. + * This method will not request to actual flush, so be sure to call {@link #flush()} + * once you want to request to flush all pending data to the actual transport. + */ + ChannelFuture write(Object msg); + + /** + * Request to write a message via this {@link ChannelHandlerContext} through the {@link ChannelPipeline}. + * This method will not request to actual flush, so be sure to call {@link #flush()} + * once you want to request to flush all pending data to the actual transport. + */ + ChannelFuture write(Object msg, ChannelPromise promise); + + /** + * Request to flush all pending messages via this ChannelOutboundInvoker. + */ + ChannelHandlerContext flush(); + + /** + * Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}. + */ + ChannelFuture writeAndFlush(Object msg, ChannelPromise promise); + + /** + * Shortcut for call {@link #write(Object)} and {@link #flush()}. + */ + ChannelFuture writeAndFlush(Object msg); + + /** + * Return the assigned {@link ChannelPipeline} + */ + ChannelPipeline pipeline(); + + /** + * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s. + */ + ByteBufAllocator alloc(); + + /** + * Return a new {@link ChannelPromise}. + */ + ChannelPromise newPromise(); + + /** + * Return an new {@link ChannelProgressivePromise} + */ +// ChannelProgressivePromise newProgressivePromise(); + + /** + * Create a new {@link ChannelFuture} which is marked as succeeded already. So {@link ChannelFuture#isSuccess()} + * will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also + * every call of blocking methods will just return without blocking. + */ + ChannelFuture newSucceededFuture(); + + /** + * Create a new {@link ChannelFuture} which is marked as failed already. So {@link ChannelFuture#isSuccess()} + * will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also + * every call of blocking methods will just return without blocking. + */ + ChannelFuture newFailedFuture(Throwable cause); + + /** + * Return a special ChannelPromise which can be reused for different operations. + *

+ * It's only supported to use + * it for {@link ChannelHandlerContext#write(Object, ChannelPromise)}. + *

+ *

+ * Be aware that the returned {@link ChannelPromise} will not support most operations and should only be used + * if you want to save an object allocation for every write operation. You will not be able to detect if the + * operation was complete, only if it failed as the implementation will call + * {@link ChannelPipeline#fireExceptionCaught(Throwable)} in this case. + *

+ * Be aware this is an expert feature and should be used with care! + */ + ChannelPromise voidPromise(); + +} diff --git a/common/src/common/net/channel/ChannelInboundHandler.java b/common/src/common/net/channel/ChannelInboundHandler.java new file mode 100644 index 0000000..97c9691 --- /dev/null +++ b/common/src/common/net/channel/ChannelInboundHandler.java @@ -0,0 +1,74 @@ +/* + * 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 common.net.channel; + +/** + * {@link ChannelHandler} which adds callbacks for state changes. This allows the user + * to hook in to state changes easily. + */ +public interface ChannelInboundHandler extends ChannelHandler { + + /** + * The {@link Channel} of the {@link ChannelHandlerContext} was registered with its {@link EventLoop} + */ + void channelRegistered(ChannelHandlerContext ctx) throws Exception; + + /** + * The {@link Channel} of the {@link ChannelHandlerContext} was unregistered from its {@link EventLoop} + */ + void channelUnregistered(ChannelHandlerContext ctx) throws Exception; + + /** + * The {@link Channel} of the {@link ChannelHandlerContext} is now active + */ + void channelActive(ChannelHandlerContext ctx) throws Exception; + + /** + * The {@link Channel} of the {@link ChannelHandlerContext} was registered is now inactive and reached its + * end of lifetime. + */ + void channelInactive(ChannelHandlerContext ctx) throws Exception; + + /** + * Invoked when the current {@link Channel} has read a message from the peer. + */ + void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; + + /** + * Invoked when the last message read by the current read operation has been consumed by + * {@link #channelRead(ChannelHandlerContext, Object)}. If {@link ChannelOption#AUTO_READ} is off, no further + * attempt to read an inbound data from the current {@link Channel} will be made until + * {@link ChannelHandlerContext#read()} is called. + */ + void channelReadComplete(ChannelHandlerContext ctx) throws Exception; + + /** + * Gets called if an user event was triggered. + */ + void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; + + /** + * Gets called once the writable state of a {@link Channel} changed. You can check the state with + * {@link Channel#isWritable()}. + */ + void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; + + /** + * Gets called if a {@link Throwable} was thrown. + */ + @Override + void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; +} diff --git a/common/src/common/net/channel/ChannelInboundHandlerAdapter.java b/common/src/common/net/channel/ChannelInboundHandlerAdapter.java new file mode 100644 index 0000000..7f6e8a2 --- /dev/null +++ b/common/src/common/net/channel/ChannelInboundHandlerAdapter.java @@ -0,0 +1,133 @@ +/* + * 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 common.net.channel; + +/** + * Abstract base class for {@link ChannelInboundHandler} implementations which provide + * implementations of all of their methods. + * + *

+ * This implementation just forward the operation to the next {@link ChannelHandler} in the + * {@link ChannelPipeline}. Sub-classes may override a method implementation to change this. + *

+ *

+ * Be aware that messages are not released after the {@link #channelRead(ChannelHandlerContext, Object)} + * method returns automatically. If you are looking for a {@link ChannelInboundHandler} implementation that + * releases the received messages automatically, please see {@link SimpleChannelInboundHandler}. + *

+ */ +public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler { + + /** + * Calls {@link ChannelHandlerContext#fireChannelRegistered()} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelRegistered(); + } + + /** + * Calls {@link ChannelHandlerContext#fireChannelUnregistered()} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelUnregistered(); + } + + /** + * Calls {@link ChannelHandlerContext#fireChannelActive()} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelActive(); + } + + /** + * Calls {@link ChannelHandlerContext#fireChannelInactive()} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelInactive(); + } + + /** + * Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ctx.fireChannelRead(msg); + } + + /** + * Calls {@link ChannelHandlerContext#fireChannelReadComplete()} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelReadComplete(); + } + + /** + * Calls {@link ChannelHandlerContext#fireUserEventTriggered(Object)} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + ctx.fireUserEventTriggered(evt); + } + + /** + * Calls {@link ChannelHandlerContext#fireChannelWritabilityChanged()} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + ctx.fireChannelWritabilityChanged(); + } + + /** + * Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward + * to the next {@link ChannelHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + throws Exception { + ctx.fireExceptionCaught(cause); + } +} diff --git a/common/src/common/net/channel/ChannelInitializer.java b/common/src/common/net/channel/ChannelInitializer.java new file mode 100644 index 0000000..31ad3ab --- /dev/null +++ b/common/src/common/net/channel/ChannelInitializer.java @@ -0,0 +1,82 @@ +/* + * 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 common.net.channel; + +import common.net.channel.ChannelHandler.Sharable; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A special {@link ChannelInboundHandler} which offers an easy way to initialize a {@link Channel} once it was + * registered to its {@link EventLoop}. + * + * Implementations are most often used in the context of {@link Bootstrap#handler(ChannelHandler)} , + * {@link ServerBootstrap#handler(ChannelHandler)} and {@link ServerBootstrap#childHandler(ChannelHandler)} to + * setup the {@link ChannelPipeline} of a {@link Channel}. + * + *
+ *
+ * public class MyChannelInitializer extends {@link ChannelInitializer} {
+ *     public void initChannel({@link Channel} channel) {
+ *         channel.pipeline().addLast("myHandler", new MyHandler());
+ *     }
+ * }
+ *
+ * {@link ServerBootstrap} bootstrap = ...;
+ * ...
+ * bootstrap.childHandler(new MyChannelInitializer());
+ * ...
+ * 
+ * Be aware that this class is marked as {@link Sharable} and so the implementation must be safe to be re-used. + * + * @param A sub-type of {@link Channel} + */ +@Sharable +public abstract class ChannelInitializer extends ChannelInboundHandlerAdapter { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class); + + /** + * This method will be called once the {@link Channel} was registered. After the method returns this instance + * will be removed from the {@link ChannelPipeline} of the {@link Channel}. + * + * @param ch the {@link Channel} which was registered. + * @throws Exception is thrown if an error occurs. In that case the {@link Channel} will be closed. + */ + protected abstract void initChannel(C ch) throws Exception; + + @Override + + public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { + ChannelPipeline pipeline = ctx.pipeline(); + boolean success = false; + try { + initChannel((C) ctx.channel()); + pipeline.remove(this); + ctx.fireChannelRegistered(); + success = true; + } catch (Throwable t) { + logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t); + } finally { + if (pipeline.context(this) != null) { + pipeline.remove(this); + } + if (!success) { + ctx.close(); + } + } + } +} diff --git a/common/src/common/net/channel/ChannelMetadata.java b/common/src/common/net/channel/ChannelMetadata.java new file mode 100644 index 0000000..375acb9 --- /dev/null +++ b/common/src/common/net/channel/ChannelMetadata.java @@ -0,0 +1,44 @@ +/* + * 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 common.net.channel; + +/** + * Represents the properties of a {@link Channel} implementation. + */ +public final class ChannelMetadata { + + private final boolean hasDisconnect; + + /** + * Create a new instance + * + * @param hasDisconnect {@code true} if and only if the channel has the {@code disconnect()} operation + * that allows a user to disconnect and then call {@link Channel#connect(SocketAddress)} + * again, such as UDP/IP. + */ + public ChannelMetadata(boolean hasDisconnect) { + this.hasDisconnect = hasDisconnect; + } + + /** + * Returns {@code true} if and only if the channel has the {@code disconnect()} operation + * that allows a user to disconnect and then call {@link Channel#connect(SocketAddress)} again, + * such as UDP/IP. + */ + public boolean hasDisconnect() { + return hasDisconnect; + } +} diff --git a/common/src/common/net/channel/ChannelOption.java b/common/src/common/net/channel/ChannelOption.java new file mode 100644 index 0000000..040213f --- /dev/null +++ b/common/src/common/net/channel/ChannelOption.java @@ -0,0 +1,111 @@ +/* + * 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 common.net.channel; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.concurrent.ConcurrentMap; + +import common.net.buffer.ByteBufAllocator; +import common.net.util.UniqueName; +import common.net.util.internal.PlatformDependent; + +/** + * A {@link ChannelOption} allows to configure a {@link ChannelConfig} in a type-safe + * way. Which {@link ChannelOption} is supported depends on the actual implementation + * of {@link ChannelConfig} and may depend on the nature of the transport it belongs + * to. + * + * @param the type of the value which is valid for the {@link ChannelOption} + */ + +public class ChannelOption extends UniqueName { + + private static final ConcurrentMap names = PlatformDependent.newConcurrentHashMap(); + + public static final ChannelOption ALLOCATOR = valueOf("ALLOCATOR"); + public static final ChannelOption RCVBUF_ALLOCATOR = valueOf("RCVBUF_ALLOCATOR"); + public static final ChannelOption MESSAGE_SIZE_ESTIMATOR = valueOf("MESSAGE_SIZE_ESTIMATOR"); + + public static final ChannelOption CONNECT_TIMEOUT_MILLIS = valueOf("CONNECT_TIMEOUT_MILLIS"); + public static final ChannelOption MAX_MESSAGES_PER_READ = valueOf("MAX_MESSAGES_PER_READ"); + public static final ChannelOption WRITE_SPIN_COUNT = valueOf("WRITE_SPIN_COUNT"); + public static final ChannelOption WRITE_BUFFER_HIGH_WATER_MARK = valueOf("WRITE_BUFFER_HIGH_WATER_MARK"); + public static final ChannelOption WRITE_BUFFER_LOW_WATER_MARK = valueOf("WRITE_BUFFER_LOW_WATER_MARK"); + + public static final ChannelOption ALLOW_HALF_CLOSURE = valueOf("ALLOW_HALF_CLOSURE"); + public static final ChannelOption AUTO_READ = valueOf("AUTO_READ"); + + /** + * @deprecated From version 5.0, {@link Channel} will not be closed on write failure. + * + * {@code true} if and only if the {@link Channel} is closed automatically and immediately on write failure. + * The default is {@code false}. + */ + @Deprecated + public static final ChannelOption AUTO_CLOSE = valueOf("AUTO_CLOSE"); + + public static final ChannelOption SO_BROADCAST = valueOf("SO_BROADCAST"); + public static final ChannelOption SO_KEEPALIVE = valueOf("SO_KEEPALIVE"); + public static final ChannelOption SO_SNDBUF = valueOf("SO_SNDBUF"); + public static final ChannelOption SO_RCVBUF = valueOf("SO_RCVBUF"); + public static final ChannelOption SO_REUSEADDR = valueOf("SO_REUSEADDR"); + public static final ChannelOption SO_LINGER = valueOf("SO_LINGER"); + public static final ChannelOption SO_BACKLOG = valueOf("SO_BACKLOG"); + public static final ChannelOption SO_TIMEOUT = valueOf("SO_TIMEOUT"); + + public static final ChannelOption IP_TOS = valueOf("IP_TOS"); + public static final ChannelOption IP_MULTICAST_ADDR = valueOf("IP_MULTICAST_ADDR"); + public static final ChannelOption IP_MULTICAST_IF = valueOf("IP_MULTICAST_IF"); + public static final ChannelOption IP_MULTICAST_TTL = valueOf("IP_MULTICAST_TTL"); + public static final ChannelOption IP_MULTICAST_LOOP_DISABLED = valueOf("IP_MULTICAST_LOOP_DISABLED"); + + public static final ChannelOption TCP_NODELAY = valueOf("TCP_NODELAY"); + + @Deprecated + public static final ChannelOption AIO_READ_TIMEOUT = valueOf("AIO_READ_TIMEOUT"); + @Deprecated + public static final ChannelOption AIO_WRITE_TIMEOUT = valueOf("AIO_WRITE_TIMEOUT"); + + @Deprecated + public static final ChannelOption DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION = + valueOf("DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION"); + + /** + * Creates a new {@link ChannelOption} with the specified {@code name}. + */ + public static ChannelOption valueOf(String name) { + return new ChannelOption(name); + } + + /** + * @deprecated Use {@link #valueOf(String)} instead. + */ + @Deprecated + protected ChannelOption(String name) { + super(names, name); + } + + /** + * Validate the value which is set for the {@link ChannelOption}. Sub-classes + * may override this for special checks. + */ + public void validate(T value) { + if (value == null) { + throw new NullPointerException("value"); + } + } +} diff --git a/common/src/common/net/channel/ChannelOutboundBuffer.java b/common/src/common/net/channel/ChannelOutboundBuffer.java new file mode 100644 index 0000000..c072d25 --- /dev/null +++ b/common/src/common/net/channel/ChannelOutboundBuffer.java @@ -0,0 +1,650 @@ +/* + * Copyright 2013 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 common.net.channel; + +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import common.net.buffer.ByteBuf; +import common.net.buffer.Unpooled; +import common.net.util.Recycler; +import common.net.util.ReferenceCountUtil; +import common.net.util.Recycler.Handle; +import common.net.util.concurrent.FastThreadLocal; +import common.net.util.internal.InternalThreadLocalMap; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * (Transport implementors only) an internal data structure used by {@link AbstractChannel} to store its pending + * outbound write requests. + * + * All the methods should only be called by the {@link EventLoop} of the {@link Channel}. + */ +public final class ChannelOutboundBuffer { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelOutboundBuffer.class); + + private static final FastThreadLocal NIO_BUFFERS = new FastThreadLocal() { + @Override + protected ByteBuffer[] initialValue() throws Exception { + return new ByteBuffer[1024]; + } + }; + + private final Channel channel; + + // Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry) + // + // The Entry that is the first in the linked-list structure that was flushed + private Entry flushedEntry; + // The Entry which is the first unflushed in the linked-list structure + private Entry unflushedEntry; + // The Entry which represents the tail of the buffer + private Entry tailEntry; + // The number of flushed entries that are not written yet + private int flushed; + + private int nioBufferCount; + private long nioBufferSize; + + private boolean inFail; + + private static final AtomicLongFieldUpdater TOTAL_PENDING_SIZE_UPDATER; + + + private volatile long totalPendingSize; + + private static final AtomicIntegerFieldUpdater WRITABLE_UPDATER; + + + private volatile int writable = 1; + + static { + AtomicIntegerFieldUpdater writableUpdater = + PlatformDependent.newAtomicIntegerFieldUpdater(ChannelOutboundBuffer.class, "writable"); + if (writableUpdater == null) { + writableUpdater = AtomicIntegerFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "writable"); + } + WRITABLE_UPDATER = writableUpdater; + + AtomicLongFieldUpdater pendingSizeUpdater = + PlatformDependent.newAtomicLongFieldUpdater(ChannelOutboundBuffer.class, "totalPendingSize"); + if (pendingSizeUpdater == null) { + pendingSizeUpdater = AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize"); + } + TOTAL_PENDING_SIZE_UPDATER = pendingSizeUpdater; + } + + ChannelOutboundBuffer(AbstractChannel channel) { + this.channel = channel; + } + + /** + * Add given message to this {@link ChannelOutboundBuffer}. The given {@link ChannelPromise} will be notified once + * the message was written. + */ + public void addMessage(Object msg, int size, ChannelPromise promise) { + Entry entry = Entry.newInstance(msg, size, total(msg), promise); + if (tailEntry == null) { + flushedEntry = null; + tailEntry = entry; + } else { + Entry tail = tailEntry; + tail.next = entry; + tailEntry = entry; + } + if (unflushedEntry == null) { + unflushedEntry = entry; + } + + // increment pending bytes after adding message to the unflushed arrays. + // See https://github.com/netty/netty/issues/1619 + incrementPendingOutboundBytes(size); + } + + /** + * Add a flush to this {@link ChannelOutboundBuffer}. This means all previous added messages are marked as flushed + * and so you will be able to handle them. + */ + public void addFlush() { + // There is no need to process all entries if there was already a flush before and no new messages + // where added in the meantime. + // + // See https://github.com/netty/netty/issues/2577 + Entry entry = unflushedEntry; + if (entry != null) { + if (flushedEntry == null) { + // there is no flushedEntry yet, so start with the entry + flushedEntry = entry; + } + do { + flushed ++; + if (!entry.promise.setUncancellable()) { + // Was cancelled so make sure we free up memory and notify about the freed bytes + int pending = entry.cancel(); + decrementPendingOutboundBytes(pending); + } + entry = entry.next; + } while (entry != null); + + // All flushed so reset unflushedEntry + unflushedEntry = null; + } + } + + /** + * Increment the pending bytes which will be written at some point. + * This method is thread-safe! + */ + void incrementPendingOutboundBytes(long size) { + if (size == 0) { + return; + } + + long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size); + if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) { + if (WRITABLE_UPDATER.compareAndSet(this, 1, 0)) { + channel.pipeline().fireChannelWritabilityChanged(); + } + } + } + + /** + * Decrement the pending bytes which will be written at some point. + * This method is thread-safe! + */ + void decrementPendingOutboundBytes(long size) { + if (size == 0) { + return; + } + + long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size); + if (newWriteBufferSize == 0 || newWriteBufferSize < channel.config().getWriteBufferLowWaterMark()) { + if (WRITABLE_UPDATER.compareAndSet(this, 0, 1)) { + channel.pipeline().fireChannelWritabilityChanged(); + } + } + } + + private static long total(Object msg) { + if (msg instanceof ByteBuf) { + return ((ByteBuf) msg).readableBytes(); + } +// if (msg instanceof FileRegion) { +// return ((FileRegion) msg).count(); +// } +// if (msg instanceof ByteBufHolder) { +// return ((ByteBufHolder) msg).content().readableBytes(); +// } + return -1; + } + + /** + * Return the current message to write or {@code null} if nothing was flushed before and so is ready to be written. + */ + public Object current() { + Entry entry = flushedEntry; + if (entry == null) { + return null; + } + + return entry.msg; + } + + /** + * Notify the {@link ChannelPromise} of the current message about writing progress. + */ + public void progress(long amount) { + Entry e = flushedEntry; + assert e != null; + ChannelPromise p = e.promise; + if (p instanceof ChannelProgressivePromise) { + long progress = e.progress + amount; + e.progress = progress; + ((ChannelProgressivePromise) p).tryProgress(progress, e.total); + } + } + + /** + * Will remove the current message, mark its {@link ChannelPromise} as success and return {@code true}. If no + * flushed message exists at the time this method is called it will return {@code false} to signal that no more + * messages are ready to be handled. + */ + public boolean remove() { + Entry e = flushedEntry; + if (e == null) { + return false; + } + Object msg = e.msg; + + ChannelPromise promise = e.promise; + int size = e.pendingSize; + + removeEntry(e); + + if (!e.cancelled) { + // only release message, notify and decrement if it was not canceled before. + ReferenceCountUtil.safeRelease(msg); + safeSuccess(promise); + decrementPendingOutboundBytes(size); + } + + // recycle the entry + e.recycle(); + + return true; + } + + /** + * Will remove the current message, mark its {@link ChannelPromise} as failure using the given {@link Throwable} + * and return {@code true}. If no flushed message exists at the time this method is called it will return + * {@code false} to signal that no more messages are ready to be handled. + */ + public boolean remove(Throwable cause) { + Entry e = flushedEntry; + if (e == null) { + return false; + } + Object msg = e.msg; + + ChannelPromise promise = e.promise; + int size = e.pendingSize; + + removeEntry(e); + + if (!e.cancelled) { + // only release message, fail and decrement if it was not canceled before. + ReferenceCountUtil.safeRelease(msg); + + safeFail(promise, cause); + decrementPendingOutboundBytes(size); + } + + // recycle the entry + e.recycle(); + + return true; + } + + private void removeEntry(Entry e) { + if (-- flushed == 0) { + // processed everything + flushedEntry = null; + if (e == tailEntry) { + tailEntry = null; + unflushedEntry = null; + } + } else { + flushedEntry = e.next; + } + } + + /** + * Removes the fully written entries and update the reader index of the partially written entry. + * This operation assumes all messages in this buffer is {@link ByteBuf}. + */ + public void removeBytes(long writtenBytes) { + for (;;) { + Object msg = current(); + if (!(msg instanceof ByteBuf)) { + assert writtenBytes == 0; + break; + } + + final ByteBuf buf = (ByteBuf) msg; + final int readerIndex = buf.readerIndex(); + final int readableBytes = buf.writerIndex() - readerIndex; + + if (readableBytes <= writtenBytes) { + if (writtenBytes != 0) { + progress(readableBytes); + writtenBytes -= readableBytes; + } + remove(); + } else { // readableBytes > writtenBytes + if (writtenBytes != 0) { + buf.readerIndex(readerIndex + (int) writtenBytes); + progress(writtenBytes); + } + break; + } + } + } + + /** + * Returns an array of direct NIO buffers if the currently pending messages are made of {@link ByteBuf} only. + * {@link #nioBufferCount()} and {@link #nioBufferSize()} will return the number of NIO buffers in the returned + * array and the total number of readable bytes of the NIO buffers respectively. + *

+ * Note that the returned array is reused and thus should not escape + * {@link AbstractChannel#doWrite(ChannelOutboundBuffer)}. + * Refer to {@link NioSocketChannel#doWrite(ChannelOutboundBuffer)} for an example. + *

+ */ + public ByteBuffer[] nioBuffers() { + long nioBufferSize = 0; + int nioBufferCount = 0; + final InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); + ByteBuffer[] nioBuffers = NIO_BUFFERS.get(threadLocalMap); + Entry entry = flushedEntry; + while (isFlushedEntry(entry) && entry.msg instanceof ByteBuf) { + if (!entry.cancelled) { + ByteBuf buf = (ByteBuf) entry.msg; + final int readerIndex = buf.readerIndex(); + final int readableBytes = buf.writerIndex() - readerIndex; + + if (readableBytes > 0) { + nioBufferSize += readableBytes; + int count = entry.count; + if (count == -1) { + //noinspection ConstantValueVariableUse + entry.count = count = buf.nioBufferCount(); + } + int neededSpace = nioBufferCount + count; + if (neededSpace > nioBuffers.length) { + nioBuffers = expandNioBufferArray(nioBuffers, neededSpace, nioBufferCount); + NIO_BUFFERS.set(threadLocalMap, nioBuffers); + } + if (count == 1) { + ByteBuffer nioBuf = entry.buf; + if (nioBuf == null) { + // cache ByteBuffer as it may need to create a new ByteBuffer instance if its a + // derived buffer + entry.buf = nioBuf = buf.internalNioBuffer(readerIndex, readableBytes); + } + nioBuffers[nioBufferCount ++] = nioBuf; + } else { + ByteBuffer[] nioBufs = entry.bufs; + if (nioBufs == null) { + // cached ByteBuffers as they may be expensive to create in terms + // of Object allocation + entry.bufs = nioBufs = buf.nioBuffers(); + } + nioBufferCount = fillBufferArray(nioBufs, nioBuffers, nioBufferCount); + } + } + } + entry = entry.next; + } + this.nioBufferCount = nioBufferCount; + this.nioBufferSize = nioBufferSize; + + return nioBuffers; + } + + private static int fillBufferArray(ByteBuffer[] nioBufs, ByteBuffer[] nioBuffers, int nioBufferCount) { + for (ByteBuffer nioBuf: nioBufs) { + if (nioBuf == null) { + break; + } + nioBuffers[nioBufferCount ++] = nioBuf; + } + return nioBufferCount; + } + + private static ByteBuffer[] expandNioBufferArray(ByteBuffer[] array, int neededSpace, int size) { + int newCapacity = array.length; + do { + // double capacity until it is big enough + // See https://github.com/netty/netty/issues/1890 + newCapacity <<= 1; + + if (newCapacity < 0) { + throw new IllegalStateException(); + } + + } while (neededSpace > newCapacity); + + ByteBuffer[] newArray = new ByteBuffer[newCapacity]; + System.arraycopy(array, 0, newArray, 0, size); + + return newArray; + } + + /** + * Returns the number of {@link ByteBuffer} that can be written out of the {@link ByteBuffer} array that was + * obtained via {@link #nioBuffers()}. This method MUST be called after {@link #nioBuffers()} + * was called. + */ + public int nioBufferCount() { + return nioBufferCount; + } + + /** + * Returns the number of bytes that can be written out of the {@link ByteBuffer} array that was + * obtained via {@link #nioBuffers()}. This method MUST be called after {@link #nioBuffers()} + * was called. + */ + public long nioBufferSize() { + return nioBufferSize; + } + + boolean isWritable() { + return writable != 0; + } + + /** + * Returns the number of flushed messages in this {@link ChannelOutboundBuffer}. + */ + public int size() { + return flushed; + } + + /** + * Returns {@code true} if there are flushed messages in this {@link ChannelOutboundBuffer} or {@code false} + * otherwise. + */ + public boolean isEmpty() { + return flushed == 0; + } + + void failFlushed(Throwable cause) { + // Make sure that this method does not reenter. A listener added to the current promise can be notified by the + // current thread in the tryFailure() call of the loop below, and the listener can trigger another fail() call + // indirectly (usually by closing the channel.) + // + // See https://github.com/netty/netty/issues/1501 + if (inFail) { + return; + } + + try { + inFail = true; + for (;;) { + if (!remove(cause)) { + break; + } + } + } finally { + inFail = false; + } + } + + void close(final ClosedChannelException cause) { + if (inFail) { + channel.eventLoop().execute(new Runnable() { + @Override + public void run() { + close(cause); + } + }); + return; + } + + inFail = true; + + if (channel.isOpen()) { + throw new IllegalStateException("close() must be invoked after the channel is closed."); + } + + if (!isEmpty()) { + throw new IllegalStateException("close() must be invoked after all flushed writes are handled."); + } + + // Release all unflushed messages. + try { + Entry e = unflushedEntry; + while (e != null) { + // Just decrease; do not trigger any events via decrementPendingOutboundBytes() + int size = e.pendingSize; + TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size); + + if (!e.cancelled) { + ReferenceCountUtil.safeRelease(e.msg); + safeFail(e.promise, cause); + } + e = e.recycleAndGetNext(); + } + } finally { + inFail = false; + } + } + + private static void safeSuccess(ChannelPromise promise) { + if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) { + logger.warn("Failed to mark a promise as success because it is done already: {}", promise); + } + } + + private static void safeFail(ChannelPromise promise, Throwable cause) { + if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) { + logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause); + } + } + + @Deprecated + public void recycle() { + // NOOP + } + + public long totalPendingWriteBytes() { + return totalPendingSize; + } + + /** + * Call {@link MessageProcessor#processMessage(Object)} for each flushed message + * in this {@link ChannelOutboundBuffer} until {@link MessageProcessor#processMessage(Object)} + * returns {@code false} or there are no more flushed messages to process. + */ + public void forEachFlushedMessage(MessageProcessor processor) throws Exception { + if (processor == null) { + throw new NullPointerException("processor"); + } + + Entry entry = flushedEntry; + if (entry == null) { + return; + } + + do { + if (!entry.cancelled) { + if (!processor.processMessage(entry.msg)) { + return; + } + } + entry = entry.next; + } while (isFlushedEntry(entry)); + } + + private boolean isFlushedEntry(Entry e) { + return e != null && e != unflushedEntry; + } + + public interface MessageProcessor { + /** + * Will be called for each flushed message until it either there are no more flushed messages or this + * method returns {@code false}. + */ + boolean processMessage(Object msg) throws Exception; + } + + static final class Entry { + private static final Recycler RECYCLER = new Recycler() { + @Override + protected Entry newObject(Handle handle) { + return new Entry(handle); + } + }; + + private final Handle handle; + Entry next; + Object msg; + ByteBuffer[] bufs; + ByteBuffer buf; + ChannelPromise promise; + long progress; + long total; + int pendingSize; + int count = -1; + boolean cancelled; + + private Entry(Handle handle) { + this.handle = handle; + } + + static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) { + Entry entry = RECYCLER.get(); + entry.msg = msg; + entry.pendingSize = size; + entry.total = total; + entry.promise = promise; + return entry; + } + + int cancel() { + if (!cancelled) { + cancelled = true; + int pSize = pendingSize; + + // release message and replace with an empty buffer + ReferenceCountUtil.safeRelease(msg); + msg = Unpooled.EMPTY_BUFFER; + + pendingSize = 0; + total = 0; + progress = 0; + bufs = null; + buf = null; + return pSize; + } + return 0; + } + + void recycle() { + next = null; + bufs = null; + buf = null; + msg = null; + promise = null; + progress = 0; + total = 0; + pendingSize = 0; + count = -1; + cancelled = false; + RECYCLER.recycle(this, handle); + } + + Entry recycleAndGetNext() { + Entry next = this.next; + recycle(); + return next; + } + } +} diff --git a/common/src/common/net/channel/ChannelOutboundHandler.java b/common/src/common/net/channel/ChannelOutboundHandler.java new file mode 100644 index 0000000..9cce0d7 --- /dev/null +++ b/common/src/common/net/channel/ChannelOutboundHandler.java @@ -0,0 +1,99 @@ +/* + * 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 common.net.channel; + +import java.net.SocketAddress; + +/** + * {@link ChannelHandler} which will get notified for IO-outbound-operations. + */ +public interface ChannelOutboundHandler extends ChannelHandler { + /** + * Called once a bind operation is made. + * + * @param ctx the {@link ChannelHandlerContext} for which the bind operation is made + * @param localAddress the {@link SocketAddress} to which it should bound + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error accour + */ + void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception; + + /** + * Called once a connect operation is made. + * + * @param ctx the {@link ChannelHandlerContext} for which the connect operation is made + * @param remoteAddress the {@link SocketAddress} to which it should connect + * @param localAddress the {@link SocketAddress} which is used as source on connect + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error accour + */ + void connect( + ChannelHandlerContext ctx, SocketAddress remoteAddress, + SocketAddress localAddress, ChannelPromise promise) throws Exception; + + /** + * Called once a disconnect operation is made. + * + * @param ctx the {@link ChannelHandlerContext} for which the disconnect operation is made + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error accour + */ + void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; + + /** + * Called once a close operation is made. + * + * @param ctx the {@link ChannelHandlerContext} for which the close operation is made + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error accour + */ + void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; + + /** + * Called once a deregister operation is made from the current registered {@link EventLoop}. + * + * @param ctx the {@link ChannelHandlerContext} for which the close operation is made + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error accour + */ + void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; + + /** + * Intercepts {@link ChannelHandlerContext#read()}. + */ + void read(ChannelHandlerContext ctx) throws Exception; + + /** + * Called once a write operation is made. The write operation will write the messages through the + * {@link ChannelPipeline}. Those are then ready to be flushed to the actual {@link Channel} once + * {@link Channel#flush()} is called + * + * @param ctx the {@link ChannelHandlerContext} for which the write operation is made + * @param msg the message to write + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error accour + */ + void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception; + + /** + * Called once a flush operation is made. The flush operation will try to flush out all previous written messages + * that are pending. + * + * @param ctx the {@link ChannelHandlerContext} for which the flush operation is made + * @throws Exception thrown if an error accour + */ + void flush(ChannelHandlerContext ctx) throws Exception; +} diff --git a/common/src/common/net/channel/ChannelOutboundHandlerAdapter.java b/common/src/common/net/channel/ChannelOutboundHandlerAdapter.java new file mode 100644 index 0000000..0ce2ac8 --- /dev/null +++ b/common/src/common/net/channel/ChannelOutboundHandlerAdapter.java @@ -0,0 +1,117 @@ +/* + * 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 common.net.channel; + +import java.net.SocketAddress; + +/** + * Skelton implementation of a {@link ChannelOutboundHandler}. This implementation just forwards each method call via + * the {@link ChannelHandlerContext}. + */ +public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler { + + /** + * Calls {@link ChannelHandlerContext#bind(SocketAddress, ChannelPromise)} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, + ChannelPromise promise) throws Exception { + ctx.bind(localAddress, promise); + } + + /** + * Calls {@link ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, + SocketAddress localAddress, ChannelPromise promise) throws Exception { + ctx.connect(remoteAddress, localAddress, promise); + } + + /** + * Calls {@link ChannelHandlerContext#disconnect(ChannelPromise)} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) + throws Exception { + ctx.disconnect(promise); + } + + /** + * Calls {@link ChannelHandlerContext#close(ChannelPromise)} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) + throws Exception { + ctx.close(promise); + } + + /** + * Calls {@link ChannelHandlerContext#close(ChannelPromise)} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + ctx.deregister(promise); + } + + /** + * Calls {@link ChannelHandlerContext#read()} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void read(ChannelHandlerContext ctx) throws Exception { + ctx.read(); + } + + /** + * Calls {@link ChannelHandlerContext#write(Object)} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + ctx.write(msg, promise); + } + + /** + * Calls {@link ChannelHandlerContext#flush()} to forward + * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + * + * Sub-classes may override this method to change behavior. + */ + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + ctx.flush(); + } +} diff --git a/common/src/common/net/channel/ChannelPipeline.java b/common/src/common/net/channel/ChannelPipeline.java new file mode 100644 index 0000000..ed5ac6f --- /dev/null +++ b/common/src/common/net/channel/ChannelPipeline.java @@ -0,0 +1,872 @@ +/* + * 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 common.net.channel; + +import java.net.SocketAddress; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import common.net.util.concurrent.EventExecutorGroup; + + +/** + * A list of {@link ChannelHandler}s which handles or intercepts inbound events and outbound operations of a + * {@link Channel}. {@link ChannelPipeline} implements an advanced form of the + * Intercepting Filter pattern + * to give a user full control over how an event is handled and how the {@link ChannelHandler}s in a pipeline + * interact with each other. + * + *

Creation of a pipeline

+ * + * Each channel has its own pipeline and it is created automatically when a new channel is created. + * + *

How an event flows in a pipeline

+ * + * The following diagram describes how I/O events are processed by {@link ChannelHandler}s in a {@link ChannelPipeline} + * typically. An I/O event is handled by either a {@link ChannelInboundHandler} or a {@link ChannelOutboundHandler} + * and be forwarded to its closest handler by calling the event propagation methods defined in + * {@link ChannelHandlerContext}, such as {@link ChannelHandlerContext#fireChannelRead(Object)} and + * {@link ChannelHandlerContext#write(Object)}. + * + *
+ *                                                 I/O Request
+ *                                            via {@link Channel} or
+ *                                        {@link ChannelHandlerContext}
+ *                                                      |
+ *  +---------------------------------------------------+---------------+
+ *  |                           ChannelPipeline         |               |
+ *  |                                                  \|/              |
+ *  |    +---------------------+            +-----------+----------+    |
+ *  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |              /|\                                  |               |
+ *  |               |                                  \|/              |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |              /|\                                  .               |
+ *  |               .                                   .               |
+ *  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
+ *  |        [ method call]                       [method call]         |
+ *  |               .                                   .               |
+ *  |               .                                  \|/              |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |              /|\                                  |               |
+ *  |               |                                  \|/              |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
+ *  |    +----------+----------+            +-----------+----------+    |
+ *  |              /|\                                  |               |
+ *  +---------------+-----------------------------------+---------------+
+ *                  |                                  \|/
+ *  +---------------+-----------------------------------+---------------+
+ *  |               |                                   |               |
+ *  |       [ Socket.read() ]                    [ Socket.write() ]     |
+ *  |                                                                   |
+ *  |  Netty Internal I/O Threads (Transport Implementation)            |
+ *  +-------------------------------------------------------------------+
+ * 
+ * An inbound event is handled by the inbound handlers in the bottom-up direction as shown on the left side of the + * diagram. An inbound handler usually handles the inbound data generated by the I/O thread on the bottom of the + * diagram. The inbound data is often read from a remote peer via the actual input operation such as + * {@link SocketChannel#read(ByteBuffer)}. If an inbound event goes beyond the top inbound handler, it is discarded + * silently, or logged if it needs your attention. + *

+ * An outbound event is handled by the outbound handler in the top-down direction as shown on the right side of the + * diagram. An outbound handler usually generates or transforms the outbound traffic such as write requests. + * If an outbound event goes beyond the bottom outbound handler, it is handled by an I/O thread associated with the + * {@link Channel}. The I/O thread often performs the actual output operation such as + * {@link SocketChannel#write(ByteBuffer)}. + *

+ * For example, let us assume that we created the following pipeline: + *

+ * {@link ChannelPipeline} p = ...;
+ * p.addLast("1", new InboundHandlerA());
+ * p.addLast("2", new InboundHandlerB());
+ * p.addLast("3", new OutboundHandlerA());
+ * p.addLast("4", new OutboundHandlerB());
+ * p.addLast("5", new InboundOutboundHandlerX());
+ * 
+ * In the example above, the class whose name starts with {@code Inbound} means it is an inbound handler. + * The class whose name starts with {@code Outbound} means it is a outbound handler. + *

+ * In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound. + * When an event goes outbound, the order is 5, 4, 3, 2, 1. On top of this principle, {@link ChannelPipeline} skips + * the evaluation of certain handlers to shorten the stack depth: + *

    + *
  • 3 and 4 don't implement {@link ChannelInboundHandler}, and therefore the actual evaluation order of an inbound + * event will be: 1, 2, and 5.
  • + *
  • 1 and 2 don't implement {@link ChannelOutboundHandler}, and therefore the actual evaluation order of a + * outbound event will be: 5, 4, and 3.
  • + *
  • If 5 implements both {@link ChannelInboundHandler} and {@link ChannelOutboundHandler}, the evaluation order of + * an inbound and a outbound event could be 125 and 543 respectively.
  • + *
+ * + *

Forwarding an event to the next handler

+ * + * As you might noticed in the diagram shows, a handler has to invoke the event propagation methods in + * {@link ChannelHandlerContext} to forward an event to its next handler. Those methods include: + *
    + *
  • Inbound event propagation methods: + *
      + *
    • {@link ChannelHandlerContext#fireChannelRegistered()}
    • + *
    • {@link ChannelHandlerContext#fireChannelActive()}
    • + *
    • {@link ChannelHandlerContext#fireChannelRead(Object)}
    • + *
    • {@link ChannelHandlerContext#fireChannelReadComplete()}
    • + *
    • {@link ChannelHandlerContext#fireExceptionCaught(Throwable)}
    • + *
    • {@link ChannelHandlerContext#fireUserEventTriggered(Object)}
    • + *
    • {@link ChannelHandlerContext#fireChannelWritabilityChanged()}
    • + *
    • {@link ChannelHandlerContext#fireChannelInactive()}
    • + *
    • {@link ChannelHandlerContext#fireChannelUnregistered()}
    • + *
    + *
  • + *
  • Outbound event propagation methods: + *
      + *
    • {@link ChannelHandlerContext#bind(SocketAddress, ChannelPromise)}
    • + *
    • {@link ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)}
    • + *
    • {@link ChannelHandlerContext#write(Object, ChannelPromise)}
    • + *
    • {@link ChannelHandlerContext#flush()}
    • + *
    • {@link ChannelHandlerContext#read()}
    • + *
    • {@link ChannelHandlerContext#disconnect(ChannelPromise)}
    • + *
    • {@link ChannelHandlerContext#close(ChannelPromise)}
    • + *
    • {@link ChannelHandlerContext#deregister(ChannelPromise)}
    • + *
    + *
  • + *
+ * + * and the following example shows how the event propagation is usually done: + * + *
+ * public class MyInboundHandler extends {@link ChannelInboundHandlerAdapter} {
+ *     {@code @Override}
+ *     public void channelActive({@link ChannelHandlerContext} ctx) {
+ *         System.out.println("Connected!");
+ *         ctx.fireChannelActive();
+ *     }
+ * }
+ *
+ * public clas MyOutboundHandler extends {@link ChannelOutboundHandlerAdapter} {
+ *     {@code @Override}
+ *     public void close({@link ChannelHandlerContext} ctx, {@link ChannelPromise} promise) {
+ *         System.out.println("Closing ..");
+ *         ctx.close(promise);
+ *     }
+ * }
+ * 
+ * + *

Building a pipeline

+ *

+ * A user is supposed to have one or more {@link ChannelHandler}s in a pipeline to receive I/O events (e.g. read) and + * to request I/O operations (e.g. write and close). For example, a typical server will have the following handlers + * in each channel's pipeline, but your mileage may vary depending on the complexity and characteristics of the + * protocol and business logic: + * + *

    + *
  1. Protocol Decoder - translates binary data (e.g. {@link ByteBuf}) into a Java object.
  2. + *
  3. Protocol Encoder - translates a Java object into binary data.
  4. + *
  5. Business Logic Handler - performs the actual business logic (e.g. database access).
  6. + *
+ * + * and it could be represented as shown in the following example: + * + *
+ * static final {@link EventExecutorGroup} group = new {@link DefaultEventExecutorGroup}(16);
+ * ...
+ *
+ * {@link ChannelPipeline} pipeline = ch.pipeline();
+ *
+ * pipeline.addLast("decoder", new MyProtocolDecoder());
+ * pipeline.addLast("encoder", new MyProtocolEncoder());
+ *
+ * // Tell the pipeline to run MyBusinessLogicHandler's event handler methods
+ * // in a different thread than an I/O thread so that the I/O thread is not blocked by
+ * // a time-consuming task.
+ * // If your business logic is fully asynchronous or finished very quickly, you don't
+ * // need to specify a group.
+ * pipeline.addLast(group, "handler", new MyBusinessLogicHandler());
+ * 
+ * + *

Thread safety

+ *

+ * A {@link ChannelHandler} can be added or removed at any time because a {@link ChannelPipeline} is thread safe. + * For example, you can insert an encryption handler when sensitive information is about to be exchanged, and remove it + * after the exchange. + */ +public interface ChannelPipeline + extends Iterable> { + + /** + * Inserts a {@link ChannelHandler} at the first position of this pipeline. + * + * @param name the name of the handler to insert first + * @param handler the handler to insert first + * + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified name or handler is {@code null} + */ + ChannelPipeline addFirst(String name, ChannelHandler handler); + + /** + * Inserts a {@link ChannelHandler} at the first position of this pipeline. + * + * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} + * methods + * @param name the name of the handler to insert first + * @param handler the handler to insert first + * + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified name or handler is {@code null} + */ + ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler); + + /** + * Appends a {@link ChannelHandler} at the last position of this pipeline. + * + * @param name the name of the handler to append + * @param handler the handler to append + * + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified name or handler is {@code null} + */ + ChannelPipeline addLast(String name, ChannelHandler handler); + + /** + * Appends a {@link ChannelHandler} at the last position of this pipeline. + * + * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} + * methods + * @param name the name of the handler to append + * @param handler the handler to append + * + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified name or handler is {@code null} + */ + ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler); + + /** + * Inserts a {@link ChannelHandler} before an existing handler of this + * pipeline. + * + * @param baseName the name of the existing handler + * @param name the name of the handler to insert before + * @param handler the handler to insert before + * + * @throws NoSuchElementException + * if there's no such entry with the specified {@code baseName} + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified baseName, name, or handler is {@code null} + */ + ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler); + + /** + * Inserts a {@link ChannelHandler} before an existing handler of this + * pipeline. + * + * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} + * methods + * @param baseName the name of the existing handler + * @param name the name of the handler to insert before + * @param handler the handler to insert before + * + * @throws NoSuchElementException + * if there's no such entry with the specified {@code baseName} + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified baseName, name, or handler is {@code null} + */ + ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler); + + /** + * Inserts a {@link ChannelHandler} after an existing handler of this + * pipeline. + * + * @param baseName the name of the existing handler + * @param name the name of the handler to insert after + * @param handler the handler to insert after + * + * @throws NoSuchElementException + * if there's no such entry with the specified {@code baseName} + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified baseName, name, or handler is {@code null} + */ + ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler); + + /** + * Inserts a {@link ChannelHandler} after an existing handler of this + * pipeline. + * + * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} + * methods + * @param baseName the name of the existing handler + * @param name the name of the handler to insert after + * @param handler the handler to insert after + * + * @throws NoSuchElementException + * if there's no such entry with the specified {@code baseName} + * @throws IllegalArgumentException + * if there's an entry with the same name already in the pipeline + * @throws NullPointerException + * if the specified baseName, name, or handler is {@code null} + */ + ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler); + + /** + * Inserts a {@link ChannelHandler}s at the first position of this pipeline. + * + * @param handlers the handlers to insert first + * + */ + ChannelPipeline addFirst(ChannelHandler... handlers); + + /** + * Inserts a {@link ChannelHandler}s at the first position of this pipeline. + * + * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}s + * methods. + * @param handlers the handlers to insert first + * + */ + ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers); + + /** + * Inserts a {@link ChannelHandler}s at the last position of this pipeline. + * + * @param handlers the handlers to insert last + * + */ + ChannelPipeline addLast(ChannelHandler... handlers); + + /** + * Inserts a {@link ChannelHandler}s at the last position of this pipeline. + * + * @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}s + * methods. + * @param handlers the handlers to insert last + * + */ + ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers); + + /** + * Removes the specified {@link ChannelHandler} from this pipeline. + * + * @param handler the {@link ChannelHandler} to remove + * + * @throws NoSuchElementException + * if there's no such handler in this pipeline + * @throws NullPointerException + * if the specified handler is {@code null} + */ + ChannelPipeline remove(ChannelHandler handler); + + /** + * Removes the {@link ChannelHandler} with the specified name from this pipeline. + * + * @param name the name under which the {@link ChannelHandler} was stored. + * + * @return the removed handler + * + * @throws NoSuchElementException + * if there's no such handler with the specified name in this pipeline + * @throws NullPointerException + * if the specified name is {@code null} + */ + ChannelHandler remove(String name); + + /** + * Removes the {@link ChannelHandler} of the specified type from this pipeline. + * + * @param the type of the handler + * @param handlerType the type of the handler + * + * @return the removed handler + * + * @throws NoSuchElementException + * if there's no such handler of the specified type in this pipeline + * @throws NullPointerException + * if the specified handler type is {@code null} + */ + T remove(Class handlerType); + + /** + * Removes the first {@link ChannelHandler} in this pipeline. + * + * @return the removed handler + * + * @throws NoSuchElementException + * if this pipeline is empty + */ + ChannelHandler removeFirst(); + + /** + * Removes the last {@link ChannelHandler} in this pipeline. + * + * @return the removed handler + * + * @throws NoSuchElementException + * if this pipeline is empty + */ + ChannelHandler removeLast(); + + /** + * Replaces the specified {@link ChannelHandler} with a new handler in this pipeline. + * + * @param oldHandler the {@link ChannelHandler} to be replaced + * @param newName the name under which the replacement should be added + * @param newHandler the {@link ChannelHandler} which is used as replacement + * + * @return itself + + * @throws NoSuchElementException + * if the specified old handler does not exist in this pipeline + * @throws IllegalArgumentException + * if a handler with the specified new name already exists in this + * pipeline, except for the handler to be replaced + * @throws NullPointerException + * if the specified old handler, new name, or new handler is + * {@code null} + */ + ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler); + + /** + * Replaces the {@link ChannelHandler} of the specified name with a new handler in this pipeline. + * + * @param oldName the name of the {@link ChannelHandler} to be replaced + * @param newName the name under which the replacement should be added + * @param newHandler the {@link ChannelHandler} which is used as replacement + * + * @return the removed handler + * + * @throws NoSuchElementException + * if the handler with the specified old name does not exist in this pipeline + * @throws IllegalArgumentException + * if a handler with the specified new name already exists in this + * pipeline, except for the handler to be replaced + * @throws NullPointerException + * if the specified old handler, new name, or new handler is + * {@code null} + */ + ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler); + + /** + * Replaces the {@link ChannelHandler} of the specified type with a new handler in this pipeline. + * + * @param oldHandlerType the type of the handler to be removed + * @param newName the name under which the replacement should be added + * @param newHandler the {@link ChannelHandler} which is used as replacement + * + * @return the removed handler + * + * @throws NoSuchElementException + * if the handler of the specified old handler type does not exist + * in this pipeline + * @throws IllegalArgumentException + * if a handler with the specified new name already exists in this + * pipeline, except for the handler to be replaced + * @throws NullPointerException + * if the specified old handler, new name, or new handler is + * {@code null} + */ + T replace(Class oldHandlerType, String newName, + ChannelHandler newHandler); + + /** + * Returns the first {@link ChannelHandler} in this pipeline. + * + * @return the first handler. {@code null} if this pipeline is empty. + */ + ChannelHandler first(); + + /** + * Returns the context of the first {@link ChannelHandler} in this pipeline. + * + * @return the context of the first handler. {@code null} if this pipeline is empty. + */ + ChannelHandlerContext firstContext(); + + /** + * Returns the last {@link ChannelHandler} in this pipeline. + * + * @return the last handler. {@code null} if this pipeline is empty. + */ + ChannelHandler last(); + + /** + * Returns the context of the last {@link ChannelHandler} in this pipeline. + * + * @return the context of the last handler. {@code null} if this pipeline is empty. + */ + ChannelHandlerContext lastContext(); + + /** + * Returns the {@link ChannelHandler} with the specified name in this + * pipeline. + * + * @return the handler with the specified name. + * {@code null} if there's no such handler in this pipeline. + */ + ChannelHandler get(String name); + + /** + * Returns the {@link ChannelHandler} of the specified type in this + * pipeline. + * + * @return the handler of the specified handler type. + * {@code null} if there's no such handler in this pipeline. + */ + T get(Class handlerType); + + /** + * Returns the context object of the specified {@link ChannelHandler} in + * this pipeline. + * + * @return the context object of the specified handler. + * {@code null} if there's no such handler in this pipeline. + */ + ChannelHandlerContext context(ChannelHandler handler); + + /** + * Returns the context object of the {@link ChannelHandler} with the + * specified name in this pipeline. + * + * @return the context object of the handler with the specified name. + * {@code null} if there's no such handler in this pipeline. + */ + ChannelHandlerContext context(String name); + + /** + * Returns the context object of the {@link ChannelHandler} of the + * specified type in this pipeline. + * + * @return the context object of the handler of the specified type. + * {@code null} if there's no such handler in this pipeline. + */ + ChannelHandlerContext context(Class handlerType); + + /** + * Returns the {@link Channel} that this pipeline is attached to. + * + * @return the channel. {@code null} if this pipeline is not attached yet. + */ + Channel channel(); + + /** + * Returns the {@link List} of the handler names. + */ + List names(); + + /** + * Converts this pipeline into an ordered {@link Map} whose keys are + * handler names and whose values are handlers. + */ + Map toMap(); + + /** + * A {@link Channel} was registered to its {@link EventLoop}. + * + * This will result in having the {@link ChannelInboundHandler#channelRegistered(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireChannelRegistered(); + + /** + * A {@link Channel} was unregistered from its {@link EventLoop}. + * + * This will result in having the {@link ChannelInboundHandler#channelUnregistered(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireChannelUnregistered(); + + /** + * A {@link Channel} is active now, which means it is connected. + * + * This will result in having the {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireChannelActive(); + + /** + * A {@link Channel} is inactive now, which means it is closed. + * + * This will result in having the {@link ChannelInboundHandler#channelInactive(ChannelHandlerContext)} method + * called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireChannelInactive(); + + /** + * A {@link Channel} received an {@link Throwable} in one of its inbound operations. + * + * This will result in having the {@link ChannelInboundHandler#exceptionCaught(ChannelHandlerContext, Throwable)} + * method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireExceptionCaught(Throwable cause); + + /** + * A {@link Channel} received an user defined event. + * + * This will result in having the {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} + * method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireUserEventTriggered(Object event); + + /** + * A {@link Channel} received a message. + * + * This will result in having the {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} + * method called of the next {@link ChannelInboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline fireChannelRead(Object msg); + + /** + * Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)} + * event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + */ + ChannelPipeline fireChannelReadComplete(); + + /** + * Triggers an {@link ChannelInboundHandler#channelWritabilityChanged(ChannelHandlerContext)} + * event to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + */ + ChannelPipeline fireChannelWritabilityChanged(); + + /** + * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method + * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture bind(SocketAddress localAddress); + + /** + * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + *

+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with + * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException} + * will be used. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress); + + /** + * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress); + + /** + * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture disconnect(); + + /** + * Request to close the {@link Channel} and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of + * an error. + * + * After it is closed it is not possible to reuse it again. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture close(); + + /** + * Request to deregister the {@link Channel} from the previous assigned {@link EventExecutor} and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + * + */ + ChannelFuture deregister(); + + /** + * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method + * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise); + + /** + * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation + * completes, either because the operation was successful or because of an error. + * + * The given {@link ChannelFuture} will be notified. + * + *

+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with + * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException} + * will be used. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise); + + /** + * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the + * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of + * an error. + * + * The given {@link ChannelPromise} will be notified and also returned. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); + + /** + * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes, + * either because the operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture disconnect(ChannelPromise promise); + + /** + * Request to close the {@link Channel} bound to this {@link ChannelPipeline} and notify the {@link ChannelFuture} + * once the operation completes, either because the operation was successful or because of + * an error. + * + * After it is closed it is not possible to reuse it again. + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture close(ChannelPromise promise); + + /** + * Request to deregister the {@link Channel} bound this {@link ChannelPipeline} from the previous assigned + * {@link EventExecutor} and notify the {@link ChannelFuture} once the operation completes, either because the + * operation was successful or because of an error. + * + * The given {@link ChannelPromise} will be notified. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelFuture deregister(ChannelPromise promise); + + /** + * Request to Read data from the {@link Channel} into the first inbound buffer, triggers an + * {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was + * read, and triggers a + * {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the + * handler can decide to continue reading. If there's a pending read operation already, this method does nothing. + *

+ * This will result in having the + * {@link ChannelOutboundHandler#read(ChannelHandlerContext)} + * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the + * {@link Channel}. + */ + ChannelPipeline read(); + + /** + * Request to write a message via this {@link ChannelPipeline}. + * This method will not request to actual flush, so be sure to call {@link #flush()} + * once you want to request to flush all pending data to the actual transport. + */ + ChannelFuture write(Object msg); + + /** + * Request to write a message via this {@link ChannelPipeline}. + * This method will not request to actual flush, so be sure to call {@link #flush()} + * once you want to request to flush all pending data to the actual transport. + */ + ChannelFuture write(Object msg, ChannelPromise promise); + + /** + * Request to flush all pending messages. + */ + ChannelPipeline flush(); + + /** + * Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}. + */ + ChannelFuture writeAndFlush(Object msg, ChannelPromise promise); + + /** + * Shortcut for call {@link #write(Object)} and {@link #flush()}. + */ + ChannelFuture writeAndFlush(Object msg); +} diff --git a/common/src/common/net/channel/ChannelPipelineException.java b/common/src/common/net/channel/ChannelPipelineException.java new file mode 100644 index 0000000..d1f8628 --- /dev/null +++ b/common/src/common/net/channel/ChannelPipelineException.java @@ -0,0 +1,52 @@ +/* + * 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 common.net.channel; + +/** + * A {@link ChannelException} which is thrown when a {@link ChannelPipeline} + * failed to execute an operation. + */ +public class ChannelPipelineException extends ChannelException { + + private static final long serialVersionUID = 3379174210419885980L; + + /** + * Creates a new instance. + */ + public ChannelPipelineException() { + } + + /** + * Creates a new instance. + */ + public ChannelPipelineException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance. + */ + public ChannelPipelineException(String message) { + super(message); + } + + /** + * Creates a new instance. + */ + public ChannelPipelineException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/channel/ChannelProgressivePromise.java b/common/src/common/net/channel/ChannelProgressivePromise.java new file mode 100644 index 0000000..530e252 --- /dev/null +++ b/common/src/common/net/channel/ChannelProgressivePromise.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013 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 common.net.channel; + +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; +import common.net.util.concurrent.ProgressiveFuture; +import common.net.util.concurrent.ProgressivePromise; + +/** + * Special {@link ChannelPromise} which will be notified once the associated bytes is transferring. + */ +public interface ChannelProgressivePromise extends ProgressivePromise, ChannelFuture, ProgressiveFuture, ChannelPromise { +// @Override +// ChannelProgressiveFuture addListener(GenericFutureListener> listener); +// +// @Override +// ChannelProgressiveFuture addListeners(GenericFutureListener>... listeners); +// +// @Override +// ChannelProgressiveFuture removeListener(GenericFutureListener> listener); +// +// @Override +// ChannelProgressiveFuture removeListeners(GenericFutureListener>... listeners); +// +// @Override +// ChannelProgressiveFuture sync() throws InterruptedException; +// +// @Override +// ChannelProgressiveFuture syncUninterruptibly(); +// +// @Override +// ChannelProgressiveFuture await() throws InterruptedException; +// +// @Override +// ChannelProgressiveFuture awaitUninterruptibly(); + + @Override + ChannelProgressivePromise addListener(GenericFutureListener> listener); + + @Override + ChannelProgressivePromise addListeners(GenericFutureListener>... listeners); + + @Override + ChannelProgressivePromise removeListener(GenericFutureListener> listener); + + @Override + ChannelProgressivePromise removeListeners(GenericFutureListener>... listeners); + + @Override + ChannelProgressivePromise sync() throws InterruptedException; + + @Override + ChannelProgressivePromise syncUninterruptibly(); + + @Override + ChannelProgressivePromise await() throws InterruptedException; + + @Override + ChannelProgressivePromise awaitUninterruptibly(); + + @Override + ChannelProgressivePromise setSuccess(Void result); + + @Override + ChannelProgressivePromise setSuccess(); + + @Override + ChannelProgressivePromise setFailure(Throwable cause); + + @Override + ChannelProgressivePromise setProgress(long progress, long total); +} diff --git a/common/src/common/net/channel/ChannelPromise.java b/common/src/common/net/channel/ChannelPromise.java new file mode 100644 index 0000000..fda7072 --- /dev/null +++ b/common/src/common/net/channel/ChannelPromise.java @@ -0,0 +1,63 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; +import common.net.util.concurrent.Promise; + +/** + * Special {@link ChannelFuture} which is writable. + */ +public interface ChannelPromise extends ChannelFuture, Promise { + + @Override + Channel channel(); + + @Override + ChannelPromise setSuccess(Void result); + + ChannelPromise setSuccess(); + + boolean trySuccess(); + + @Override + ChannelPromise setFailure(Throwable cause); + + @Override + ChannelPromise addListener(GenericFutureListener> listener); + + @Override + ChannelPromise addListeners(GenericFutureListener>... listeners); + + @Override + ChannelPromise removeListener(GenericFutureListener> listener); + + @Override + ChannelPromise removeListeners(GenericFutureListener>... listeners); + + @Override + ChannelPromise sync() throws InterruptedException; + + @Override + ChannelPromise syncUninterruptibly(); + + @Override + ChannelPromise await() throws InterruptedException; + + @Override + ChannelPromise awaitUninterruptibly(); +} diff --git a/common/src/common/net/channel/CompleteChannelFuture.java b/common/src/common/net/channel/CompleteChannelFuture.java new file mode 100644 index 0000000..2e8dac6 --- /dev/null +++ b/common/src/common/net/channel/CompleteChannelFuture.java @@ -0,0 +1,107 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.CompleteFuture; +import common.net.util.concurrent.EventExecutor; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; + +/** + * A skeletal {@link ChannelFuture} implementation which represents a + * {@link ChannelFuture} which has been completed already. + */ +abstract class CompleteChannelFuture extends CompleteFuture implements ChannelFuture { + + private final Channel channel; + + /** + * Creates a new instance. + * + * @param channel the {@link Channel} associated with this future + */ + protected CompleteChannelFuture(Channel channel, EventExecutor executor) { + super(executor); + if (channel == null) { + throw new NullPointerException("channel"); + } + this.channel = channel; + } + + @Override + protected EventExecutor executor() { + EventExecutor e = super.executor(); + if (e == null) { + return channel().eventLoop(); + } else { + return e; + } + } + + @Override + public ChannelFuture addListener(GenericFutureListener> listener) { + super.addListener(listener); + return this; + } + + @Override + public ChannelFuture addListeners(GenericFutureListener>... listeners) { + super.addListeners(listeners); + return this; + } + + @Override + public ChannelFuture removeListener(GenericFutureListener> listener) { + super.removeListener(listener); + return this; + } + + @Override + public ChannelFuture removeListeners(GenericFutureListener>... listeners) { + super.removeListeners(listeners); + return this; + } + + @Override + public ChannelFuture syncUninterruptibly() { + return this; + } + + @Override + public ChannelFuture sync() throws InterruptedException { + return this; + } + + @Override + public ChannelFuture await() throws InterruptedException { + return this; + } + + @Override + public ChannelFuture awaitUninterruptibly() { + return this; + } + + @Override + public Channel channel() { + return channel; + } + + @Override + public Void getNow() { + return null; + } +} diff --git a/common/src/common/net/channel/ConnectTimeoutException.java b/common/src/common/net/channel/ConnectTimeoutException.java new file mode 100644 index 0000000..831cbe6 --- /dev/null +++ b/common/src/common/net/channel/ConnectTimeoutException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 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 common.net.channel; + +import java.net.ConnectException; + +/** + * {@link ConnectException} which will be thrown if a connection could + * not be established because of a connection timeout. + */ +public class ConnectTimeoutException extends ConnectException { + private static final long serialVersionUID = 2317065249988317463L; + + public ConnectTimeoutException(String msg) { + super(msg); + } + + public ConnectTimeoutException() { + } +} diff --git a/common/src/common/net/channel/DefaultChannelConfig.java b/common/src/common/net/channel/DefaultChannelConfig.java new file mode 100644 index 0000000..6985206 --- /dev/null +++ b/common/src/common/net/channel/DefaultChannelConfig.java @@ -0,0 +1,355 @@ +/* + * 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 common.net.channel; + +import static common.net.channel.ChannelOption.ALLOCATOR; +import static common.net.channel.ChannelOption.AUTO_CLOSE; +import static common.net.channel.ChannelOption.AUTO_READ; +import static common.net.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS; +import static common.net.channel.ChannelOption.MAX_MESSAGES_PER_READ; +import static common.net.channel.ChannelOption.MESSAGE_SIZE_ESTIMATOR; +import static common.net.channel.ChannelOption.RCVBUF_ALLOCATOR; +import static common.net.channel.ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK; +import static common.net.channel.ChannelOption.WRITE_BUFFER_LOW_WATER_MARK; +import static common.net.channel.ChannelOption.WRITE_SPIN_COUNT; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import common.net.buffer.ByteBufAllocator; +import common.net.channel.nio.AbstractNioByteChannel; + +/** + * The default {@link SocketChannelConfig} implementation. + */ +public class DefaultChannelConfig implements ChannelConfig { + + private static final RecvByteBufAllocator DEFAULT_RCVBUF_ALLOCATOR = AdaptiveRecvByteBufAllocator.DEFAULT; + private static final MessageSizeEstimator DEFAULT_MSG_SIZE_ESTIMATOR = DefaultMessageSizeEstimator.DEFAULT; + + private static final int DEFAULT_CONNECT_TIMEOUT = 30000; + + protected final Channel channel; + + private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private volatile RecvByteBufAllocator rcvBufAllocator = DEFAULT_RCVBUF_ALLOCATOR; + private volatile MessageSizeEstimator msgSizeEstimator = DEFAULT_MSG_SIZE_ESTIMATOR; + + private volatile int connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT; + private volatile int maxMessagesPerRead; + private volatile int writeSpinCount = 16; + private volatile boolean autoRead = true; + private volatile boolean autoClose = true; + private volatile int writeBufferHighWaterMark = 64 * 1024; + private volatile int writeBufferLowWaterMark = 32 * 1024; + + public DefaultChannelConfig(Channel channel) { + if (channel == null) { + throw new NullPointerException("channel"); + } + this.channel = channel; + + if (channel instanceof ServerChannel || channel instanceof AbstractNioByteChannel) { + // Server channels: Accept as many incoming connections as possible. + // NIO byte channels: Implemented to reduce unnecessary system calls even if it's > 1. + // See https://github.com/netty/netty/issues/2079 + // TODO: Add some property to ChannelMetadata so we can remove the ugly instanceof + maxMessagesPerRead = 16; + } else { + maxMessagesPerRead = 1; + } + } + + @Override + + public Map, Object> getOptions() { + return getOptions( + null, + CONNECT_TIMEOUT_MILLIS, MAX_MESSAGES_PER_READ, WRITE_SPIN_COUNT, + ALLOCATOR, AUTO_READ, AUTO_CLOSE, RCVBUF_ALLOCATOR, WRITE_BUFFER_HIGH_WATER_MARK, + WRITE_BUFFER_LOW_WATER_MARK, MESSAGE_SIZE_ESTIMATOR); + } + + protected Map, Object> getOptions( + Map, Object> result, ChannelOption... options) { + if (result == null) { + result = new IdentityHashMap, Object>(); + } + for (ChannelOption o: options) { + result.put(o, getOption(o)); + } + return result; + } + + + @Override + public boolean setOptions(Map, ?> options) { + if (options == null) { + throw new NullPointerException("options"); + } + + boolean setAllOptions = true; + for (Entry, ?> e: options.entrySet()) { + if (!setOption((ChannelOption) e.getKey(), e.getValue())) { + setAllOptions = false; + } + } + + return setAllOptions; + } + + @Override + + public T getOption(ChannelOption option) { + if (option == null) { + throw new NullPointerException("option"); + } + + if (option == CONNECT_TIMEOUT_MILLIS) { + return (T) Integer.valueOf(getConnectTimeoutMillis()); + } + if (option == MAX_MESSAGES_PER_READ) { + return (T) Integer.valueOf(getMaxMessagesPerRead()); + } + if (option == WRITE_SPIN_COUNT) { + return (T) Integer.valueOf(getWriteSpinCount()); + } + if (option == ALLOCATOR) { + return (T) getAllocator(); + } + if (option == RCVBUF_ALLOCATOR) { + return (T) getRecvByteBufAllocator(); + } + if (option == AUTO_READ) { + return (T) Boolean.valueOf(isAutoRead()); + } + if (option == AUTO_CLOSE) { + return (T) Boolean.valueOf(isAutoClose()); + } + if (option == WRITE_BUFFER_HIGH_WATER_MARK) { + return (T) Integer.valueOf(getWriteBufferHighWaterMark()); + } + if (option == WRITE_BUFFER_LOW_WATER_MARK) { + return (T) Integer.valueOf(getWriteBufferLowWaterMark()); + } + if (option == MESSAGE_SIZE_ESTIMATOR) { + return (T) getMessageSizeEstimator(); + } + return null; + } + + @Override + + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == CONNECT_TIMEOUT_MILLIS) { + setConnectTimeoutMillis((Integer) value); + } else if (option == MAX_MESSAGES_PER_READ) { + setMaxMessagesPerRead((Integer) value); + } else if (option == WRITE_SPIN_COUNT) { + setWriteSpinCount((Integer) value); + } else if (option == ALLOCATOR) { + setAllocator((ByteBufAllocator) value); + } else if (option == RCVBUF_ALLOCATOR) { + setRecvByteBufAllocator((RecvByteBufAllocator) value); + } else if (option == AUTO_READ) { + setAutoRead((Boolean) value); + } else if (option == AUTO_CLOSE) { + setAutoClose((Boolean) value); + } else if (option == WRITE_BUFFER_HIGH_WATER_MARK) { + setWriteBufferHighWaterMark((Integer) value); + } else if (option == WRITE_BUFFER_LOW_WATER_MARK) { + setWriteBufferLowWaterMark((Integer) value); + } else if (option == MESSAGE_SIZE_ESTIMATOR) { + setMessageSizeEstimator((MessageSizeEstimator) value); + } else { + return false; + } + + return true; + } + + protected void validate(ChannelOption option, T value) { + if (option == null) { + throw new NullPointerException("option"); + } + option.validate(value); + } + + @Override + public int getConnectTimeoutMillis() { + return connectTimeoutMillis; + } + + @Override + public ChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + if (connectTimeoutMillis < 0) { + throw new IllegalArgumentException(String.format( + "connectTimeoutMillis: %d (expected: >= 0)", connectTimeoutMillis)); + } + this.connectTimeoutMillis = connectTimeoutMillis; + return this; + } + + @Override + public int getMaxMessagesPerRead() { + return maxMessagesPerRead; + } + + @Override + public ChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + if (maxMessagesPerRead <= 0) { + throw new IllegalArgumentException("maxMessagesPerRead: " + maxMessagesPerRead + " (expected: > 0)"); + } + this.maxMessagesPerRead = maxMessagesPerRead; + return this; + } + + @Override + public int getWriteSpinCount() { + return writeSpinCount; + } + + @Override + public ChannelConfig setWriteSpinCount(int writeSpinCount) { + if (writeSpinCount <= 0) { + throw new IllegalArgumentException( + "writeSpinCount must be a positive integer."); + } + this.writeSpinCount = writeSpinCount; + return this; + } + + @Override + public ByteBufAllocator getAllocator() { + return allocator; + } + + @Override + public ChannelConfig setAllocator(ByteBufAllocator allocator) { + if (allocator == null) { + throw new NullPointerException("allocator"); + } + this.allocator = allocator; + return this; + } + + @Override + public RecvByteBufAllocator getRecvByteBufAllocator() { + return rcvBufAllocator; + } + + @Override + public ChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + if (allocator == null) { + throw new NullPointerException("allocator"); + } + rcvBufAllocator = allocator; + return this; + } + + @Override + public boolean isAutoRead() { + return autoRead; + } + + @Override + public ChannelConfig setAutoRead(boolean autoRead) { + boolean oldAutoRead = this.autoRead; + this.autoRead = autoRead; + if (autoRead && !oldAutoRead) { + channel.read(); + } else if (!autoRead && oldAutoRead) { + autoReadCleared(); + } + return this; + } + + /** + * Is called once {@link #setAutoRead(boolean)} is called with {@code false} and {@link #isAutoRead()} was + * {@code true} before. + */ + protected void autoReadCleared() { } + + @Override + public boolean isAutoClose() { + return autoClose; + } + + @Override + public ChannelConfig setAutoClose(boolean autoClose) { + this.autoClose = autoClose; + return this; + } + + @Override + public int getWriteBufferHighWaterMark() { + return writeBufferHighWaterMark; + } + + @Override + public ChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + if (writeBufferHighWaterMark < getWriteBufferLowWaterMark()) { + throw new IllegalArgumentException( + "writeBufferHighWaterMark cannot be less than " + + "writeBufferLowWaterMark (" + getWriteBufferLowWaterMark() + "): " + + writeBufferHighWaterMark); + } + if (writeBufferHighWaterMark < 0) { + throw new IllegalArgumentException( + "writeBufferHighWaterMark must be >= 0"); + } + this.writeBufferHighWaterMark = writeBufferHighWaterMark; + return this; + } + + @Override + public int getWriteBufferLowWaterMark() { + return writeBufferLowWaterMark; + } + + @Override + public ChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + if (writeBufferLowWaterMark > getWriteBufferHighWaterMark()) { + throw new IllegalArgumentException( + "writeBufferLowWaterMark cannot be greater than " + + "writeBufferHighWaterMark (" + getWriteBufferHighWaterMark() + "): " + + writeBufferLowWaterMark); + } + if (writeBufferLowWaterMark < 0) { + throw new IllegalArgumentException( + "writeBufferLowWaterMark must be >= 0"); + } + this.writeBufferLowWaterMark = writeBufferLowWaterMark; + return this; + } + + @Override + public MessageSizeEstimator getMessageSizeEstimator() { + return msgSizeEstimator; + } + + @Override + public ChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + if (estimator == null) { + throw new NullPointerException("estimator"); + } + msgSizeEstimator = estimator; + return this; + } +} diff --git a/common/src/common/net/channel/DefaultChannelHandlerContext.java b/common/src/common/net/channel/DefaultChannelHandlerContext.java new file mode 100644 index 0000000..9c24e1e --- /dev/null +++ b/common/src/common/net/channel/DefaultChannelHandlerContext.java @@ -0,0 +1,45 @@ +/* +* Copyright 2014 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 common.net.channel; + +import common.net.util.concurrent.EventExecutorGroup; + +final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext { + + private final ChannelHandler handler; + + DefaultChannelHandlerContext( + DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) { + super(pipeline, group, name, isInbound(handler), isOutbound(handler)); + if (handler == null) { + throw new NullPointerException("handler"); + } + this.handler = handler; + } + + @Override + public ChannelHandler handler() { + return handler; + } + + private static boolean isInbound(ChannelHandler handler) { + return handler instanceof ChannelInboundHandler; + } + + private static boolean isOutbound(ChannelHandler handler) { + return handler instanceof ChannelOutboundHandler; + } +} diff --git a/common/src/common/net/channel/DefaultChannelPipeline.java b/common/src/common/net/channel/DefaultChannelPipeline.java new file mode 100644 index 0000000..73e66c9 --- /dev/null +++ b/common/src/common/net/channel/DefaultChannelPipeline.java @@ -0,0 +1,1067 @@ +/* + * 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 common.net.channel; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import common.net.channel.Channel.Unsafe; +import common.net.util.ReferenceCountUtil; +import common.net.util.concurrent.EventExecutor; +import common.net.util.concurrent.EventExecutorGroup; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * The default {@link ChannelPipeline} implementation. It is usually created + * by a {@link Channel} implementation when the {@link Channel} is created. + */ +final class DefaultChannelPipeline implements ChannelPipeline { + + static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class); + + + private static final WeakHashMap, String>[] nameCaches = + new WeakHashMap[Runtime.getRuntime().availableProcessors()]; + + static { + for (int i = 0; i < nameCaches.length; i ++) { + nameCaches[i] = new WeakHashMap, String>(); + } + } + + final AbstractChannel channel; + + final AbstractChannelHandlerContext head; + final AbstractChannelHandlerContext tail; + + private final Map name2ctx = + new HashMap(4); + + final Map childExecutors = + new IdentityHashMap(); + + public DefaultChannelPipeline(AbstractChannel channel) { + if (channel == null) { + throw new NullPointerException("channel"); + } + this.channel = channel; + + tail = new TailContext(this); + head = new HeadContext(this); + + head.next = tail; + tail.prev = head; + } + + @Override + public Channel channel() { + return channel; + } + + @Override + public ChannelPipeline addFirst(String name, ChannelHandler handler) { + return addFirst(null, name, handler); + } + + @Override + public ChannelPipeline addFirst(EventExecutorGroup group, final String name, ChannelHandler handler) { + synchronized (this) { + checkDuplicateName(name); + AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler); + addFirst0(name, newCtx); + } + + return this; + } + + private void addFirst0(String name, AbstractChannelHandlerContext newCtx) { + checkMultiplicity(newCtx); + + AbstractChannelHandlerContext nextCtx = head.next; + newCtx.prev = head; + newCtx.next = nextCtx; + head.next = newCtx; + nextCtx.prev = newCtx; + + name2ctx.put(name, newCtx); + + callHandlerAdded(newCtx); + } + + @Override + public ChannelPipeline addLast(String name, ChannelHandler handler) { + return addLast(null, name, handler); + } + + @Override + public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) { + synchronized (this) { + checkDuplicateName(name); + + AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler); + addLast0(name, newCtx); + } + + return this; + } + + private void addLast0(final String name, AbstractChannelHandlerContext newCtx) { + checkMultiplicity(newCtx); + + AbstractChannelHandlerContext prev = tail.prev; + newCtx.prev = prev; + newCtx.next = tail; + prev.next = newCtx; + tail.prev = newCtx; + + name2ctx.put(name, newCtx); + + callHandlerAdded(newCtx); + } + + @Override + public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) { + return addBefore(null, baseName, name, handler); + } + + @Override + public ChannelPipeline addBefore( + EventExecutorGroup group, String baseName, final String name, ChannelHandler handler) { + synchronized (this) { + AbstractChannelHandlerContext ctx = getContextOrDie(baseName); + checkDuplicateName(name); + AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler); + addBefore0(name, ctx, newCtx); + } + return this; + } + + private void addBefore0( + final String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) { + checkMultiplicity(newCtx); + + newCtx.prev = ctx.prev; + newCtx.next = ctx; + ctx.prev.next = newCtx; + ctx.prev = newCtx; + + name2ctx.put(name, newCtx); + + callHandlerAdded(newCtx); + } + + @Override + public ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler) { + return addAfter(null, baseName, name, handler); + } + + @Override + public ChannelPipeline addAfter( + EventExecutorGroup group, String baseName, final String name, ChannelHandler handler) { + synchronized (this) { + AbstractChannelHandlerContext ctx = getContextOrDie(baseName); + checkDuplicateName(name); + AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler); + + addAfter0(name, ctx, newCtx); + } + + return this; + } + + private void addAfter0(final String name, AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) { + checkDuplicateName(name); + checkMultiplicity(newCtx); + + newCtx.prev = ctx; + newCtx.next = ctx.next; + ctx.next.prev = newCtx; + ctx.next = newCtx; + + name2ctx.put(name, newCtx); + + callHandlerAdded(newCtx); + } + + @Override + public ChannelPipeline addFirst(ChannelHandler... handlers) { + return addFirst(null, handlers); + } + + @Override + public ChannelPipeline addFirst(EventExecutorGroup executor, ChannelHandler... handlers) { + if (handlers == null) { + throw new NullPointerException("handlers"); + } + if (handlers.length == 0 || handlers[0] == null) { + return this; + } + + int size; + for (size = 1; size < handlers.length; size ++) { + if (handlers[size] == null) { + break; + } + } + + for (int i = size - 1; i >= 0; i --) { + ChannelHandler h = handlers[i]; + addFirst(executor, generateName(h), h); + } + + return this; + } + + @Override + public ChannelPipeline addLast(ChannelHandler... handlers) { + return addLast(null, handlers); + } + + @Override + public ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) { + if (handlers == null) { + throw new NullPointerException("handlers"); + } + + for (ChannelHandler h: handlers) { + if (h == null) { + break; + } + addLast(executor, generateName(h), h); + } + + return this; + } + + private String generateName(ChannelHandler handler) { + WeakHashMap, String> cache = nameCaches[(int) (Thread.currentThread().getId() % nameCaches.length)]; + Class handlerType = handler.getClass(); + String name; + synchronized (cache) { + name = cache.get(handlerType); + if (name == null) { + name = generateName0(handlerType); + cache.put(handlerType, name); + } + } + + synchronized (this) { + // It's not very likely for a user to put more than one handler of the same type, but make sure to avoid + // any name conflicts. Note that we don't cache the names generated here. + if (name2ctx.containsKey(name)) { + String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'. + for (int i = 1;; i ++) { + String newName = baseName + i; + if (!name2ctx.containsKey(newName)) { + name = newName; + break; + } + } + } + } + + return name; + } + + private static String generateName0(Class handlerType) { + return StringUtil.simpleClassName(handlerType) + "#0"; + } + + @Override + public ChannelPipeline remove(ChannelHandler handler) { + remove(getContextOrDie(handler)); + return this; + } + + @Override + public ChannelHandler remove(String name) { + return remove(getContextOrDie(name)).handler(); + } + + + @Override + public T remove(Class handlerType) { + return (T) remove(getContextOrDie(handlerType)).handler(); + } + + private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) { + assert ctx != head && ctx != tail; + + AbstractChannelHandlerContext context; + Future future; + + synchronized (this) { + if (!ctx.channel().isRegistered() || ctx.executor().inEventLoop()) { + remove0(ctx); + return ctx; + } else { + future = ctx.executor().submit(new Runnable() { + @Override + public void run() { + synchronized (DefaultChannelPipeline.this) { + remove0(ctx); + } + } + }); + context = ctx; + } + } + + // Run the following 'waiting' code outside of the above synchronized block + // in order to avoid deadlock + + waitForFuture(future); + + return context; + } + + void remove0(AbstractChannelHandlerContext ctx) { + AbstractChannelHandlerContext prev = ctx.prev; + AbstractChannelHandlerContext next = ctx.next; + prev.next = next; + next.prev = prev; + name2ctx.remove(ctx.name()); + callHandlerRemoved(ctx); + } + + @Override + public ChannelHandler removeFirst() { + if (head.next == tail) { + throw new NoSuchElementException(); + } + return remove(head.next).handler(); + } + + @Override + public ChannelHandler removeLast() { + if (head.next == tail) { + throw new NoSuchElementException(); + } + return remove(tail.prev).handler(); + } + + @Override + public ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler) { + replace(getContextOrDie(oldHandler), newName, newHandler); + return this; + } + + @Override + public ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler) { + return replace(getContextOrDie(oldName), newName, newHandler); + } + + @Override + + public T replace( + Class oldHandlerType, String newName, ChannelHandler newHandler) { + return (T) replace(getContextOrDie(oldHandlerType), newName, newHandler); + } + + private ChannelHandler replace( + final AbstractChannelHandlerContext ctx, final String newName, + ChannelHandler newHandler) { + + assert ctx != head && ctx != tail; + + Future future; + synchronized (this) { + boolean sameName = ctx.name().equals(newName); + if (!sameName) { + checkDuplicateName(newName); + } + + final AbstractChannelHandlerContext newCtx = + new DefaultChannelHandlerContext(this, ctx.executor, newName, newHandler); + + if (!newCtx.channel().isRegistered() || newCtx.executor().inEventLoop()) { + replace0(ctx, newName, newCtx); + return ctx.handler(); + } else { + future = newCtx.executor().submit(new Runnable() { + @Override + public void run() { + synchronized (DefaultChannelPipeline.this) { + replace0(ctx, newName, newCtx); + } + } + }); + } + } + + // Run the following 'waiting' code outside of the above synchronized block + // in order to avoid deadlock + + waitForFuture(future); + + return ctx.handler(); + } + + private void replace0(AbstractChannelHandlerContext oldCtx, String newName, + AbstractChannelHandlerContext newCtx) { + checkMultiplicity(newCtx); + + AbstractChannelHandlerContext prev = oldCtx.prev; + AbstractChannelHandlerContext next = oldCtx.next; + newCtx.prev = prev; + newCtx.next = next; + + // Finish the replacement of oldCtx with newCtx in the linked list. + // Note that this doesn't mean events will be sent to the new handler immediately + // because we are currently at the event handler thread and no more than one handler methods can be invoked + // at the same time (we ensured that in replace().) + prev.next = newCtx; + next.prev = newCtx; + + if (!oldCtx.name().equals(newName)) { + name2ctx.remove(oldCtx.name()); + } + name2ctx.put(newName, newCtx); + + // update the reference to the replacement so forward of buffered content will work correctly + oldCtx.prev = newCtx; + oldCtx.next = newCtx; + + // Invoke newHandler.handlerAdded() first (i.e. before oldHandler.handlerRemoved() is invoked) + // because callHandlerRemoved() will trigger inboundBufferUpdated() or flush() on newHandler and those + // event handlers must be called after handlerAdded(). + callHandlerAdded(newCtx); + callHandlerRemoved(oldCtx); + } + + private static void checkMultiplicity(ChannelHandlerContext ctx) { + ChannelHandler handler = ctx.handler(); + if (handler instanceof ChannelHandlerAdapter) { + ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler; + if (!h.isSharable() && h.added) { + throw new ChannelPipelineException( + h.getClass().getName() + + " is not a @Sharable handler, so can't be added or removed multiple times."); + } + h.added = true; + } + } + + private void callHandlerAdded(final ChannelHandlerContext ctx) { + if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) { + ctx.executor().execute(new Runnable() { + @Override + public void run() { + callHandlerAdded0(ctx); + } + }); + return; + } + callHandlerAdded0(ctx); + } + + private void callHandlerAdded0(final ChannelHandlerContext ctx) { + try { + ctx.handler().handlerAdded(ctx); + } catch (Throwable t) { + boolean removed = false; + try { + remove((AbstractChannelHandlerContext) ctx); + removed = true; + } catch (Throwable t2) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to remove a handler: " + ctx.name(), t2); + } + } + + if (removed) { + fireExceptionCaught(new ChannelPipelineException( + ctx.handler().getClass().getName() + + ".handlerAdded() has thrown an exception; removed.", t)); + } else { + fireExceptionCaught(new ChannelPipelineException( + ctx.handler().getClass().getName() + + ".handlerAdded() has thrown an exception; also failed to remove.", t)); + } + } + } + + private void callHandlerRemoved(final AbstractChannelHandlerContext ctx) { + if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) { + ctx.executor().execute(new Runnable() { + @Override + public void run() { + callHandlerRemoved0(ctx); + } + }); + return; + } + callHandlerRemoved0(ctx); + } + + private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) { + // Notify the complete removal. + try { + ctx.handler().handlerRemoved(ctx); + ctx.setRemoved(); + } catch (Throwable t) { + fireExceptionCaught(new ChannelPipelineException( + ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t)); + } + } + + /** + * Waits for a future to finish. If the task is interrupted, then the current thread will be interrupted. + * It is expected that the task performs any appropriate locking. + *

+ * If the internal call throws a {@link Throwable}, but it is not an instance of {@link Error} or + * {@link RuntimeException}, then it is wrapped inside a {@link ChannelPipelineException} and that is + * thrown instead.

+ * + * @param future wait for this future + * @see Future#get() + * @throws Error if the task threw this. + * @throws RuntimeException if the task threw this. + * @throws ChannelPipelineException with a {@link Throwable} as a cause, if the task threw another type of + * {@link Throwable}. + */ + private static void waitForFuture(Future future) { + try { + future.get(); + } catch (ExecutionException ex) { + // In the arbitrary case, we can throw Error, RuntimeException, and Exception + PlatformDependent.throwException(ex.getCause()); + } catch (InterruptedException ex) { + // Interrupt the calling thread (note that this method is not called from the event loop) + Thread.currentThread().interrupt(); + } + } + + @Override + public ChannelHandler first() { + ChannelHandlerContext first = firstContext(); + if (first == null) { + return null; + } + return first.handler(); + } + + @Override + public ChannelHandlerContext firstContext() { + AbstractChannelHandlerContext first = head.next; + if (first == tail) { + return null; + } + return head.next; + } + + @Override + public ChannelHandler last() { + AbstractChannelHandlerContext last = tail.prev; + if (last == head) { + return null; + } + return last.handler(); + } + + @Override + public ChannelHandlerContext lastContext() { + AbstractChannelHandlerContext last = tail.prev; + if (last == head) { + return null; + } + return last; + } + + @Override + public ChannelHandler get(String name) { + ChannelHandlerContext ctx = context(name); + if (ctx == null) { + return null; + } else { + return ctx.handler(); + } + } + + + @Override + public T get(Class handlerType) { + ChannelHandlerContext ctx = context(handlerType); + if (ctx == null) { + return null; + } else { + return (T) ctx.handler(); + } + } + + @Override + public ChannelHandlerContext context(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + synchronized (this) { + return name2ctx.get(name); + } + } + + @Override + public ChannelHandlerContext context(ChannelHandler handler) { + if (handler == null) { + throw new NullPointerException("handler"); + } + + AbstractChannelHandlerContext ctx = head.next; + for (;;) { + + if (ctx == null) { + return null; + } + + if (ctx.handler() == handler) { + return ctx; + } + + ctx = ctx.next; + } + } + + @Override + public ChannelHandlerContext context(Class handlerType) { + if (handlerType == null) { + throw new NullPointerException("handlerType"); + } + + AbstractChannelHandlerContext ctx = head.next; + for (;;) { + if (ctx == null) { + return null; + } + if (handlerType.isAssignableFrom(ctx.handler().getClass())) { + return ctx; + } + ctx = ctx.next; + } + } + + @Override + public List names() { + List list = new ArrayList(); + AbstractChannelHandlerContext ctx = head.next; + for (;;) { + if (ctx == null) { + return list; + } + list.add(ctx.name()); + ctx = ctx.next; + } + } + + @Override + public Map toMap() { + Map map = new LinkedHashMap(); + AbstractChannelHandlerContext ctx = head.next; + for (;;) { + if (ctx == tail) { + return map; + } + map.put(ctx.name(), ctx.handler()); + ctx = ctx.next; + } + } + + @Override + public Iterator> iterator() { + return toMap().entrySet().iterator(); + } + + /** + * Returns the {@link String} representation of this pipeline. + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(StringUtil.simpleClassName(this)); + buf.append('{'); + AbstractChannelHandlerContext ctx = head.next; + for (;;) { + if (ctx == tail) { + break; + } + + buf.append('('); + buf.append(ctx.name()); + buf.append(" = "); + buf.append(ctx.handler().getClass().getName()); + buf.append(')'); + + ctx = ctx.next; + if (ctx == tail) { + break; + } + + buf.append(", "); + } + buf.append('}'); + return buf.toString(); + } + + @Override + public ChannelPipeline fireChannelRegistered() { + head.fireChannelRegistered(); + return this; + } + + @Override + public ChannelPipeline fireChannelUnregistered() { + head.fireChannelUnregistered(); + + // Remove all handlers sequentially if channel is closed and unregistered. + if (!channel.isOpen()) { + teardownAll(); + } + return this; + } + + /** + * Removes all handlers from the pipeline one by one from tail (exclusive) to head (inclusive) to trigger + * handlerRemoved(). Note that the tail handler is excluded because it's neither an outbound handler nor it + * does anything in handlerRemoved(). + */ + private void teardownAll() { + tail.prev.teardown(); + } + + @Override + public ChannelPipeline fireChannelActive() { + head.fireChannelActive(); + + if (channel.config().isAutoRead()) { + channel.read(); + } + + return this; + } + + @Override + public ChannelPipeline fireChannelInactive() { + head.fireChannelInactive(); + return this; + } + + @Override + public ChannelPipeline fireExceptionCaught(Throwable cause) { + head.fireExceptionCaught(cause); + return this; + } + + @Override + public ChannelPipeline fireUserEventTriggered(Object event) { + head.fireUserEventTriggered(event); + return this; + } + + @Override + public ChannelPipeline fireChannelRead(Object msg) { + head.fireChannelRead(msg); + return this; + } + + @Override + public ChannelPipeline fireChannelReadComplete() { + head.fireChannelReadComplete(); + if (channel.config().isAutoRead()) { + read(); + } + return this; + } + + @Override + public ChannelPipeline fireChannelWritabilityChanged() { + head.fireChannelWritabilityChanged(); + return this; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return tail.bind(localAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return tail.connect(remoteAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return tail.connect(remoteAddress, localAddress); + } + + @Override + public ChannelFuture disconnect() { + return tail.disconnect(); + } + + @Override + public ChannelFuture close() { + return tail.close(); + } + + @Override + public ChannelFuture deregister() { + return tail.deregister(); + } + + @Override + public ChannelPipeline flush() { + tail.flush(); + return this; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return tail.bind(localAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return tail.connect(remoteAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return tail.connect(remoteAddress, localAddress, promise); + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return tail.disconnect(promise); + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return tail.close(promise); + } + + @Override + public ChannelFuture deregister(final ChannelPromise promise) { + return tail.deregister(promise); + } + + @Override + public ChannelPipeline read() { + tail.read(); + return this; + } + + @Override + public ChannelFuture write(Object msg) { + return tail.write(msg); + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return tail.write(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return tail.writeAndFlush(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return tail.writeAndFlush(msg); + } + + private void checkDuplicateName(String name) { + if (name2ctx.containsKey(name)) { + throw new IllegalArgumentException("Duplicate handler name: " + name); + } + } + + private AbstractChannelHandlerContext getContextOrDie(String name) { + AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name); + if (ctx == null) { + throw new NoSuchElementException(name); + } else { + return ctx; + } + } + + private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) { + AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler); + if (ctx == null) { + throw new NoSuchElementException(handler.getClass().getName()); + } else { + return ctx; + } + } + + private AbstractChannelHandlerContext getContextOrDie(Class handlerType) { + AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handlerType); + if (ctx == null) { + throw new NoSuchElementException(handlerType.getName()); + } else { + return ctx; + } + } + + // A special catch-all handler that handles both bytes and messages. + static final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler { + + private static final String TAIL_NAME = generateName0(TailContext.class); + + TailContext(DefaultChannelPipeline pipeline) { + super(pipeline, null, TAIL_NAME, true, false); + } + + @Override + public ChannelHandler handler() { + return this; + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.warn( + "An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " + + "It usually means the last handler in the pipeline did not handle the exception.", cause); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + try { + logger.debug( + "Discarded inbound message {} that reached at the tail of the pipeline. " + + "Please check your pipeline configuration.", msg); + } finally { + ReferenceCountUtil.release(msg); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { } + } + + static final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler { + + private static final String HEAD_NAME = generateName0(HeadContext.class); + + protected final Unsafe unsafe; + + HeadContext(DefaultChannelPipeline pipeline) { + super(pipeline, null, HEAD_NAME, false, true); + unsafe = pipeline.channel().unsafe(); + } + + @Override + public ChannelHandler handler() { + return this; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + // NOOP + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + // NOOP + } + + @Override + public void bind( + ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) + throws Exception { + unsafe.bind(localAddress, promise); + } + + @Override + public void connect( + ChannelHandlerContext ctx, + SocketAddress remoteAddress, SocketAddress localAddress, + ChannelPromise promise) throws Exception { + unsafe.connect(remoteAddress, localAddress, promise); + } + + @Override + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + unsafe.disconnect(promise); + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + unsafe.close(promise); + } + + @Override + public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + unsafe.deregister(promise); + } + + @Override + public void read(ChannelHandlerContext ctx) { + unsafe.beginRead(); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + unsafe.write(msg, promise); + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + unsafe.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.fireExceptionCaught(cause); + } + } +} diff --git a/common/src/common/net/channel/DefaultChannelPromise.java b/common/src/common/net/channel/DefaultChannelPromise.java new file mode 100644 index 0000000..02cc235 --- /dev/null +++ b/common/src/common/net/channel/DefaultChannelPromise.java @@ -0,0 +1,159 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.DefaultPromise; +import common.net.util.concurrent.EventExecutor; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; + +/** + * The default {@link ChannelPromise} implementation. It is recommended to use {@link Channel#newPromise()} to create + * a new {@link ChannelPromise} rather than calling the constructor explicitly. + */ +public class DefaultChannelPromise extends DefaultPromise implements ChannelPromise { + + private final Channel channel; +// private long checkpoint; + + /** + * Creates a new instance. + * + * @param channel + * the {@link Channel} associated with this future + */ + public DefaultChannelPromise(Channel channel) { + this.channel = channel; + } + + /** + * Creates a new instance. + * + * @param channel + * the {@link Channel} associated with this future + */ + public DefaultChannelPromise(Channel channel, EventExecutor executor) { + super(executor); + this.channel = channel; + } + + @Override + protected EventExecutor executor() { + EventExecutor e = super.executor(); + if (e == null) { + return channel().eventLoop(); + } else { + return e; + } + } + + @Override + public Channel channel() { + return channel; + } + + @Override + public ChannelPromise setSuccess() { + return setSuccess(null); + } + + @Override + public ChannelPromise setSuccess(Void result) { + super.setSuccess(result); + return this; + } + + @Override + public boolean trySuccess() { + return trySuccess(null); + } + + @Override + public ChannelPromise setFailure(Throwable cause) { + super.setFailure(cause); + return this; + } + + @Override + public ChannelPromise addListener(GenericFutureListener> listener) { + super.addListener(listener); + return this; + } + + @Override + public ChannelPromise addListeners(GenericFutureListener>... listeners) { + super.addListeners(listeners); + return this; + } + + @Override + public ChannelPromise removeListener(GenericFutureListener> listener) { + super.removeListener(listener); + return this; + } + + @Override + public ChannelPromise removeListeners(GenericFutureListener>... listeners) { + super.removeListeners(listeners); + return this; + } + + @Override + public ChannelPromise sync() throws InterruptedException { + super.sync(); + return this; + } + + @Override + public ChannelPromise syncUninterruptibly() { + super.syncUninterruptibly(); + return this; + } + + @Override + public ChannelPromise await() throws InterruptedException { + super.await(); + return this; + } + + @Override + public ChannelPromise awaitUninterruptibly() { + super.awaitUninterruptibly(); + return this; + } + +// @Override +// public long flushCheckpoint() { +// return checkpoint; +// } + +// @Override +// public void flushCheckpoint(long checkpoint) { +// this.checkpoint = checkpoint; +// } + +// @Override +// public ChannelPromise promise() { +// return this; +// } + + @Override + protected void checkDeadLock() { + if (channel().isRegistered()) { + super.checkDeadLock(); + } + } +} diff --git a/common/src/common/net/channel/DefaultMessageSizeEstimator.java b/common/src/common/net/channel/DefaultMessageSizeEstimator.java new file mode 100644 index 0000000..0862e96 --- /dev/null +++ b/common/src/common/net/channel/DefaultMessageSizeEstimator.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013 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 common.net.channel; + +import common.net.buffer.ByteBuf; + +/** + * Default {@link MessageSizeEstimator} implementation which supports the estimation of the size of + * {@link ByteBuf}, {@link ByteBufHolder} and {@link FileRegion}. + */ +public final class DefaultMessageSizeEstimator implements MessageSizeEstimator { + + private static final class HandleImpl implements Handle { + private final int unknownSize; + + private HandleImpl(int unknownSize) { + this.unknownSize = unknownSize; + } + + @Override + public int size(Object msg) { + if (msg instanceof ByteBuf) { + return ((ByteBuf) msg).readableBytes(); + } +// if (msg instanceof ByteBufHolder) { +// return ((ByteBufHolder) msg).content().readableBytes(); +// } +// if (msg instanceof FileRegion) { +// return 0; +// } + return unknownSize; + } + } + + /** + * Return the default implementation which returns {@code -1} for unknown messages. + */ + public static final MessageSizeEstimator DEFAULT = new DefaultMessageSizeEstimator(0); + + private final Handle handle; + + /** + * Create a new instance + * + * @param unknownSize The size which is returned for unknown messages. + */ + public DefaultMessageSizeEstimator(int unknownSize) { + if (unknownSize < 0) { + throw new IllegalArgumentException("unknownSize: " + unknownSize + " (expected: >= 0)"); + } + handle = new HandleImpl(unknownSize); + } + + @Override + public Handle newHandle() { + return handle; + } +} diff --git a/common/src/common/net/channel/EventLoop.java b/common/src/common/net/channel/EventLoop.java new file mode 100644 index 0000000..8daea7a --- /dev/null +++ b/common/src/common/net/channel/EventLoop.java @@ -0,0 +1,30 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.EventExecutor; + +/** + * Will handle all the I/O-Operations for a {@link Channel} once it was registered. + * + * One {@link EventLoop} instance will usually handle more then one {@link Channel} but this may depend on + * implementation details and internals. + * + */ +public interface EventLoop extends EventExecutor, EventLoopGroup { + @Override + EventLoopGroup parent(); +} diff --git a/common/src/common/net/channel/EventLoopException.java b/common/src/common/net/channel/EventLoopException.java new file mode 100644 index 0000000..02f9ffa --- /dev/null +++ b/common/src/common/net/channel/EventLoopException.java @@ -0,0 +1,41 @@ +/* + * 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 common.net.channel; + +/** + * Special {@link ChannelException} which will be thrown by {@link EventLoop} and {@link EventLoopGroup} + * implementations when an error occurs. + */ +public class EventLoopException extends ChannelException { + + private static final long serialVersionUID = -8969100344583703616L; + + public EventLoopException() { + } + + public EventLoopException(String message, Throwable cause) { + super(message, cause); + } + + public EventLoopException(String message) { + super(message); + } + + public EventLoopException(Throwable cause) { + super(cause); + } + +} diff --git a/common/src/common/net/channel/EventLoopGroup.java b/common/src/common/net/channel/EventLoopGroup.java new file mode 100644 index 0000000..e058491 --- /dev/null +++ b/common/src/common/net/channel/EventLoopGroup.java @@ -0,0 +1,43 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.EventExecutorGroup; + +/** + * Special {@link EventExecutorGroup} which allows to register {@link Channel}'s that get + * processed for later selection during the event loop. + * + */ +public interface EventLoopGroup extends EventExecutorGroup { + /** + * Return the next {@link EventLoop} to use + */ + @Override + EventLoop next(); + + /** + * Register a {@link Channel} with this {@link EventLoop}. The returned {@link ChannelFuture} + * will get notified once the registration was complete. + */ + ChannelFuture register(Channel channel); + + /** + * Register a {@link Channel} with this {@link EventLoop}. The passed {@link ChannelFuture} + * will get notified once the registration was complete and also will get returned. + */ + ChannelFuture register(Channel channel, ChannelPromise promise); +} diff --git a/common/src/common/net/channel/FailedChannelFuture.java b/common/src/common/net/channel/FailedChannelFuture.java new file mode 100644 index 0000000..b36b461 --- /dev/null +++ b/common/src/common/net/channel/FailedChannelFuture.java @@ -0,0 +1,65 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.EventExecutor; +import common.net.util.internal.PlatformDependent; + +/** + * The {@link CompleteChannelFuture} which is failed already. It is + * recommended to use {@link Channel#newFailedFuture(Throwable)} + * instead of calling the constructor of this future. + */ +final class FailedChannelFuture extends CompleteChannelFuture { + + private final Throwable cause; + + /** + * Creates a new instance. + * + * @param channel the {@link Channel} associated with this future + * @param cause the cause of failure + */ + FailedChannelFuture(Channel channel, EventExecutor executor, Throwable cause) { + super(channel, executor); + if (cause == null) { + throw new NullPointerException("cause"); + } + this.cause = cause; + } + + @Override + public Throwable cause() { + return cause; + } + + @Override + public boolean isSuccess() { + return false; + } + + @Override + public ChannelFuture sync() { + PlatformDependent.throwException(cause); + return this; + } + + @Override + public ChannelFuture syncUninterruptibly() { + PlatformDependent.throwException(cause); + return this; + } +} diff --git a/common/src/common/net/channel/MessageSizeEstimator.java b/common/src/common/net/channel/MessageSizeEstimator.java new file mode 100644 index 0000000..cdefc87 --- /dev/null +++ b/common/src/common/net/channel/MessageSizeEstimator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 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 common.net.channel; + +/** + * Responsible to estimate size of a message. The size represent how much memory the message will ca. reserve in + * memory. + */ +public interface MessageSizeEstimator { + + /** + * Creates a new handle. The handle provides the actual operations. + */ + Handle newHandle(); + + interface Handle { + + /** + * Calculate the size of the given message. + * + * @param msg The message for which the size should be calculated + * @return size The size in bytes. The returned size must be >= 0 + */ + int size(Object msg); + } +} diff --git a/common/src/common/net/channel/MultithreadEventLoopGroup.java b/common/src/common/net/channel/MultithreadEventLoopGroup.java new file mode 100644 index 0000000..0a5f4e1 --- /dev/null +++ b/common/src/common/net/channel/MultithreadEventLoopGroup.java @@ -0,0 +1,71 @@ +/* + * 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 common.net.channel; + +import java.util.concurrent.ThreadFactory; + +import common.net.util.concurrent.DefaultThreadFactory; +import common.net.util.concurrent.MultithreadEventExecutorGroup; +import common.net.util.internal.SystemPropertyUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Abstract base class for {@link EventLoopGroup} implementations that handles their tasks with multiple threads at + * the same time. + */ +public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(MultithreadEventLoopGroup.class); + + private static final int DEFAULT_EVENT_LOOP_THREADS; + + static { + DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( + "game.net.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); + + if (logger.isDebugEnabled()) { + logger.debug("-Dgame.net.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS); + } + } + + /** + * @see {@link MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, ThreadFactory, Object...)} + */ + protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) { + super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args); + } + + @Override + protected ThreadFactory newDefaultThreadFactory() { + return new DefaultThreadFactory(getClass(), Thread.MAX_PRIORITY); + } + + @Override + public EventLoop next() { + return (EventLoop) super.next(); + } + + @Override + public ChannelFuture register(Channel channel) { + return next().register(channel); + } + + @Override + public ChannelFuture register(Channel channel, ChannelPromise promise) { + return next().register(channel, promise); + } +} diff --git a/common/src/common/net/channel/RecvByteBufAllocator.java b/common/src/common/net/channel/RecvByteBufAllocator.java new file mode 100644 index 0000000..f90ff3f --- /dev/null +++ b/common/src/common/net/channel/RecvByteBufAllocator.java @@ -0,0 +1,54 @@ +/* + * 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 common.net.channel; + +import common.net.buffer.ByteBuf; +import common.net.buffer.ByteBufAllocator; + +/** + * Allocates a new receive buffer whose capacity is probably large enough to read all inbound data and small enough + * not to waste its space. + */ +public interface RecvByteBufAllocator { + + /** + * Creates a new handle. The handle provides the actual operations and keeps the internal information which is + * required for predicting an optimal buffer capacity. + */ + Handle newHandle(); + + interface Handle { + /** + * Creates a new receive buffer whose capacity is probably large enough to read all inbound data and small + * enough not to waste its space. + */ + ByteBuf allocate(ByteBufAllocator alloc); + + /** + * Similar to {@link #allocate(ByteBufAllocator)} except that it does not allocate anything but just tells the + * capacity. + */ + int guess(); + + /** + * Records the the actual number of read bytes in the previous read operation so that the allocator allocates + * the buffer with potentially more correct capacity. + * + * @param actualReadBytes the actual number of read bytes in the previous read operation + */ + void record(int actualReadBytes); + } +} diff --git a/common/src/common/net/channel/ServerChannel.java b/common/src/common/net/channel/ServerChannel.java new file mode 100644 index 0000000..7dedcaa --- /dev/null +++ b/common/src/common/net/channel/ServerChannel.java @@ -0,0 +1,25 @@ +/* + * 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 common.net.channel; + +/** + * A {@link Channel} that accepts an incoming connection attempt and creates + * its child {@link Channel}s by accepting them. {@link ServerSocketChannel} is + * a good example. + */ +public interface ServerChannel extends Channel { + // This is a tag interface. +} diff --git a/common/src/common/net/channel/SimpleChannelInboundHandler.java b/common/src/common/net/channel/SimpleChannelInboundHandler.java new file mode 100644 index 0000000..68b003e --- /dev/null +++ b/common/src/common/net/channel/SimpleChannelInboundHandler.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013 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 common.net.channel; + +import common.net.util.ReferenceCountUtil; +import common.net.util.internal.TypeParameterMatcher; + +/** + * {@link ChannelInboundHandlerAdapter} which allows to explicit only handle a specific type of messages. + * + * For example here is an implementation which only handle {@link String} messages. + * + *
+ *     public class StringHandler extends
+ *             {@link SimpleChannelInboundHandler}<{@link String}> {
+ *
+ *         {@code @Override}
+ *         protected void channelRead0({@link ChannelHandlerContext} ctx, {@link String} message)
+ *                 throws {@link Exception} {
+ *             System.out.println(message);
+ *         }
+ *     }
+ * 
+ * + * Be aware that depending of the constructor parameters it will release all handled messages by pass them to + * {@link ReferenceCountUtil#release(Object)}. In this case you may need to use + * {@link ReferenceCountUtil#retain(Object)} if you pass the object to the next handler in the {@link ChannelPipeline}. + * + *

Forward compatibility notice

+ *

+ * Please keep in mind that {@link #channelRead0(ChannelHandlerContext, I)} will be renamed to + * {@code messageReceived(ChannelHandlerContext, I)} in 5.0. + *

+ */ +public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter { + + private final TypeParameterMatcher matcher; + private final boolean autoRelease; + + /** + * @see {@link #SimpleChannelInboundHandler(boolean)} with {@code true} as boolean parameter. + */ + protected SimpleChannelInboundHandler() { + this(true); + } + + /** + * Create a new instance which will try to detect the types to match out of the type parameter of the class. + * + * @param autoRelease {@code true} if handled messages should be released automatically by pass them to + * {@link ReferenceCountUtil#release(Object)}. + */ + protected SimpleChannelInboundHandler(boolean autoRelease) { + matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I"); + this.autoRelease = autoRelease; + } + + /** + * @see {@link #SimpleChannelInboundHandler(Class, boolean)} with {@code true} as boolean value. + */ + protected SimpleChannelInboundHandler(Class inboundMessageType) { + this(inboundMessageType, true); + } + + /** + * Create a new instance + * + * @param inboundMessageType The type of messages to match + * @param autoRelease {@code true} if handled messages should be released automatically by pass them to + * {@link ReferenceCountUtil#release(Object)}. + */ + protected SimpleChannelInboundHandler(Class inboundMessageType, boolean autoRelease) { + matcher = TypeParameterMatcher.get(inboundMessageType); + this.autoRelease = autoRelease; + } + + /** + * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next + * {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + */ + public boolean acceptInboundMessage(Object msg) throws Exception { + return matcher.match(msg); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + boolean release = true; + try { + if (acceptInboundMessage(msg)) { + + I imsg = (I) msg; + channelRead0(ctx, imsg); + } else { + release = false; + ctx.fireChannelRead(msg); + } + } finally { + if (autoRelease && release) { + ReferenceCountUtil.release(msg); + } + } + } + + /** + * Please keep in mind that this method will be renamed to + * {@code messageReceived(ChannelHandlerContext, I)} in 5.0. + * + * Is called for each message of type {@link I}. + * + * @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler} + * belongs to + * @param msg the message to handle + * @throws Exception is thrown if an error occurred + */ + protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception; +} diff --git a/common/src/common/net/channel/SingleThreadEventLoop.java b/common/src/common/net/channel/SingleThreadEventLoop.java new file mode 100644 index 0000000..ed45956 --- /dev/null +++ b/common/src/common/net/channel/SingleThreadEventLoop.java @@ -0,0 +1,72 @@ +/* + * 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 common.net.channel; + +import java.util.concurrent.ThreadFactory; + +import common.net.util.concurrent.SingleThreadEventExecutor; + +/** + * Abstract base class for {@link EventLoop}'s that execute all its submitted tasks in a single thread. + * + */ +public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop { + + /** + * @see {@link SingleThreadEventExecutor#SingleThreadEventExecutor(EventExecutorGroup, ThreadFactory, boolean)} + */ + protected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) { + super(parent, threadFactory, addTaskWakesUp); + } + + @Override + public EventLoopGroup parent() { + return (EventLoopGroup) super.parent(); + } + + @Override + public EventLoop next() { + return (EventLoop) super.next(); + } + + @Override + public ChannelFuture register(Channel channel) { + return register(channel, new DefaultChannelPromise(channel, this)); + } + + @Override + public ChannelFuture register(final Channel channel, final ChannelPromise promise) { + if (channel == null) { + throw new NullPointerException("channel"); + } + if (promise == null) { + throw new NullPointerException("promise"); + } + + channel.unsafe().register(this, promise); + return promise; + } + + @Override + protected boolean wakesUpForTask(Runnable task) { + return !(task instanceof NonWakeupRunnable); + } + + /** + * Marker interface for {@linkRunnable} that will not trigger an {@link #wakeup(boolean)} in all cases. + */ + interface NonWakeupRunnable extends Runnable { } +} diff --git a/common/src/common/net/channel/SucceededChannelFuture.java b/common/src/common/net/channel/SucceededChannelFuture.java new file mode 100644 index 0000000..f2fafea --- /dev/null +++ b/common/src/common/net/channel/SucceededChannelFuture.java @@ -0,0 +1,45 @@ +/* + * 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 common.net.channel; + +import common.net.util.concurrent.EventExecutor; + +/** + * The {@link CompleteChannelFuture} which is succeeded already. It is + * recommended to use {@link Channel#newSucceededFuture()} instead of + * calling the constructor of this future. + */ +final class SucceededChannelFuture extends CompleteChannelFuture { + + /** + * Creates a new instance. + * + * @param channel the {@link Channel} associated with this future + */ + SucceededChannelFuture(Channel channel, EventExecutor executor) { + super(channel, executor); + } + + @Override + public Throwable cause() { + return null; + } + + @Override + public boolean isSuccess() { + return true; + } +} diff --git a/common/src/common/net/channel/VoidChannelPromise.java b/common/src/common/net/channel/VoidChannelPromise.java new file mode 100644 index 0000000..1fcbb12 --- /dev/null +++ b/common/src/common/net/channel/VoidChannelPromise.java @@ -0,0 +1,205 @@ +/* + * 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 common.net.channel; + +import java.util.concurrent.TimeUnit; + +import common.net.util.concurrent.AbstractFuture; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; + +final class VoidChannelPromise extends AbstractFuture implements ChannelPromise { + + private final Channel channel; + private final boolean fireException; + + /** + * Creates a new instance. + * + * @param channel the {@link Channel} associated with this future + */ + VoidChannelPromise(Channel channel, boolean fireException) { + if (channel == null) { + throw new NullPointerException("channel"); + } + this.channel = channel; + this.fireException = fireException; + } + + @Override + public VoidChannelPromise addListener(GenericFutureListener> listener) { + fail(); + return this; + } + + @Override + public VoidChannelPromise addListeners(GenericFutureListener>... listeners) { + fail(); + return this; + } + + @Override + public VoidChannelPromise removeListener(GenericFutureListener> listener) { + // NOOP + return this; + } + + @Override + public VoidChannelPromise removeListeners(GenericFutureListener>... listeners) { + // NOOP + return this; + } + + @Override + public VoidChannelPromise await() throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + return this; + } + + @Override + public boolean await(long timeout, TimeUnit unit) { + fail(); + return false; + } + + @Override + public boolean await(long timeoutMillis) { + fail(); + return false; + } + + @Override + public VoidChannelPromise awaitUninterruptibly() { + fail(); + return this; + } + + @Override + public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { + fail(); + return false; + } + + @Override + public boolean awaitUninterruptibly(long timeoutMillis) { + fail(); + return false; + } + + @Override + public Channel channel() { + return channel; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public boolean isSuccess() { + return false; + } + + @Override + public boolean setUncancellable() { + return true; + } + + @Override + public boolean isCancellable() { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public Throwable cause() { + return null; + } + + @Override + public VoidChannelPromise sync() { + fail(); + return this; + } + + @Override + public VoidChannelPromise syncUninterruptibly() { + fail(); + return this; + } + @Override + public VoidChannelPromise setFailure(Throwable cause) { + fireException(cause); + return this; + } + + @Override + public VoidChannelPromise setSuccess() { + return this; + } + + @Override + public boolean tryFailure(Throwable cause) { + fireException(cause); + return false; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean trySuccess() { + return false; + } + + private static void fail() { + throw new IllegalStateException("void future"); + } + + @Override + public VoidChannelPromise setSuccess(Void result) { + return this; + } + + @Override + public boolean trySuccess(Void result) { + return false; + } + + @Override + public Void getNow() { + return null; + } + + private void fireException(Throwable cause) { + // Only fire the exception if the channel is open and registered + // if not the pipeline is not setup and so it would hit the tail + // of the pipeline. + // See https://github.com/netty/netty/issues/1517 + if (fireException && channel.isRegistered()) { + channel.pipeline().fireExceptionCaught(cause); + } + } +} diff --git a/common/src/common/net/channel/local/LocalAddress.java b/common/src/common/net/channel/local/LocalAddress.java new file mode 100644 index 0000000..95f6e43 --- /dev/null +++ b/common/src/common/net/channel/local/LocalAddress.java @@ -0,0 +1,94 @@ +/* + * 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 common.net.channel.local; + +import java.net.SocketAddress; + +import common.net.channel.Channel; + +/** + * An endpoint in the local transport. Each endpoint is identified by a unique + * case-insensitive string. + */ +public final class LocalAddress extends SocketAddress implements Comparable { + + private static final long serialVersionUID = 4644331421130916435L; + + public static final LocalAddress ANY = new LocalAddress("ANY"); + + private final String id; + private final String strVal; + + /** + * Creates a new ephemeral port based on the ID of the specified channel. + * Note that we prepend an upper-case character so that it never conflicts with + * the addresses created by a user, which are always lower-cased on construction time. + */ + LocalAddress(Channel channel) { + StringBuilder buf = new StringBuilder(16); + buf.append("local:E"); + buf.append(Long.toHexString(channel.hashCode() & 0xFFFFFFFFL | 0x100000000L)); + buf.setCharAt(7, ':'); + id = buf.substring(6); + strVal = buf.toString(); + } + + /** + * Creates a new instance with the specified ID. + */ + public LocalAddress(String id) { + if (id == null) { + throw new NullPointerException("id"); + } + id = id.trim().toLowerCase(); + if (id.isEmpty()) { + throw new IllegalArgumentException("empty id"); + } + this.id = id; + strVal = "local:" + id; + } + + /** + * Returns the ID of this address. + */ + public String id() { + return id; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LocalAddress)) { + return false; + } + + return id.equals(((LocalAddress) o).id); + } + + @Override + public int compareTo(LocalAddress o) { + return id.compareTo(o.id); + } + + @Override + public String toString() { + return strVal; + } +} diff --git a/common/src/common/net/channel/local/LocalChannel.java b/common/src/common/net/channel/local/LocalChannel.java new file mode 100644 index 0000000..c5590c9 --- /dev/null +++ b/common/src/common/net/channel/local/LocalChannel.java @@ -0,0 +1,383 @@ +/* + * 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 common.net.channel.local; + +import java.net.SocketAddress; +import java.nio.channels.AlreadyConnectedException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ConnectionPendingException; +import java.nio.channels.NotYetConnectedException; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Queue; + +import common.net.channel.AbstractChannel; +import common.net.channel.Channel; +import common.net.channel.ChannelConfig; +import common.net.channel.ChannelException; +import common.net.channel.ChannelMetadata; +import common.net.channel.ChannelOutboundBuffer; +import common.net.channel.ChannelPipeline; +import common.net.channel.ChannelPromise; +import common.net.channel.DefaultChannelConfig; +import common.net.channel.EventLoop; +import common.net.channel.SingleThreadEventLoop; +import common.net.util.ReferenceCountUtil; +import common.net.util.concurrent.SingleThreadEventExecutor; +import common.net.util.internal.InternalThreadLocalMap; + +/** + * A {@link Channel} for the local transport. + */ +public class LocalChannel extends AbstractChannel { + + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + + private static final int MAX_READER_STACK_DEPTH = 8; + + private final ChannelConfig config = new DefaultChannelConfig(this); + private final Queue inboundBuffer = new ArrayDeque(); + private final Runnable readTask = new Runnable() { + @Override + public void run() { + ChannelPipeline pipeline = pipeline(); + for (;;) { + Object m = inboundBuffer.poll(); + if (m == null) { + break; + } + pipeline.fireChannelRead(m); + } + pipeline.fireChannelReadComplete(); + } + }; + + private final Runnable shutdownHook = new Runnable() { + @Override + public void run() { + unsafe().close(unsafe().voidPromise()); + } + }; + + private volatile int state; // 0 - open, 1 - bound, 2 - connected, 3 - closed + private volatile LocalChannel peer; + private volatile LocalAddress localAddress; + private volatile LocalAddress remoteAddress; + private volatile ChannelPromise connectPromise; + private volatile boolean readInProgress; + private volatile boolean registerInProgress; + + public LocalChannel() { + super(null); + } + + LocalChannel(LocalServerChannel parent, LocalChannel peer) { + super(parent); + this.peer = peer; + localAddress = parent.localAddress(); + remoteAddress = peer.localAddress(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public ChannelConfig config() { + return config; + } + + @Override + public LocalServerChannel parent() { + return (LocalServerChannel) super.parent(); + } + + @Override + public LocalAddress localAddress() { + return (LocalAddress) super.localAddress(); + } + + @Override + public LocalAddress remoteAddress() { + return (LocalAddress) super.remoteAddress(); + } + + @Override + public boolean isOpen() { + return state < 3; + } + + @Override + public boolean isActive() { + return state == 2; + } + + @Override + protected AbstractUnsafe newUnsafe() { + return new LocalUnsafe(); + } + + @Override + protected boolean isCompatible(EventLoop loop) { + return loop instanceof SingleThreadEventLoop; + } + + @Override + protected SocketAddress localAddress0() { + return localAddress; + } + + @Override + protected SocketAddress remoteAddress0() { + return remoteAddress; + } + + @Override + protected void doRegister() throws Exception { + // Check if both peer and parent are non-null because this channel was created by a LocalServerChannel. + // This is needed as a peer may not be null also if a LocalChannel was connected before and + // deregistered / registered later again. + // + // See https://github.com/netty/netty/issues/2400 + if (peer != null && parent() != null) { + // Store the peer in a local variable as it may be set to null if doClose() is called. + // Because of this we also set registerInProgress to true as we check for this in doClose() and make sure + // we delay the fireChannelInactive() to be fired after the fireChannelActive() and so keep the correct + // order of events. + // + // See https://github.com/netty/netty/issues/2144 + final LocalChannel peer = this.peer; + registerInProgress = true; + state = 2; + + peer.remoteAddress = parent().localAddress(); + peer.state = 2; + + // Always call peer.eventLoop().execute() even if peer.eventLoop().inEventLoop() is true. + // This ensures that if both channels are on the same event loop, the peer's channelActive + // event is triggered *after* this channel's channelRegistered event, so that this channel's + // pipeline is fully initialized by ChannelInitializer before any channelRead events. + peer.eventLoop().execute(new Runnable() { + @Override + public void run() { + registerInProgress = false; + peer.pipeline().fireChannelActive(); + peer.connectPromise.setSuccess(); + } + }); + } + ((SingleThreadEventExecutor) eventLoop()).addShutdownHook(shutdownHook); + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + this.localAddress = + LocalChannelRegistry.register(this, this.localAddress, + localAddress); + state = 1; + } + + @Override + protected void doDisconnect() throws Exception { + doClose(); + } + + @Override + protected void doClose() throws Exception { + if (state <= 2) { + // Update all internal state before the closeFuture is notified. + if (localAddress != null) { + if (parent() == null) { + LocalChannelRegistry.unregister(localAddress); + } + localAddress = null; + } + state = 3; + } + + final LocalChannel peer = this.peer; + if (peer != null && peer.isActive()) { + // Need to execute the close in the correct EventLoop + // See https://github.com/netty/netty/issues/1777 + EventLoop eventLoop = peer.eventLoop(); + + // Also check if the registration was not done yet. In this case we submit the close to the EventLoop + // to make sure it is run after the registration completes. + // + // See https://github.com/netty/netty/issues/2144 + if (eventLoop.inEventLoop() && !registerInProgress) { + peer.unsafe().close(unsafe().voidPromise()); + } else { + peer.eventLoop().execute(new Runnable() { + @Override + public void run() { + peer.unsafe().close(unsafe().voidPromise()); + } + }); + } + this.peer = null; + } + } + + @Override + protected void doDeregister() throws Exception { + // Just remove the shutdownHook as this Channel may be closed later or registered to another EventLoop + ((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook); + } + + @Override + protected void doBeginRead() throws Exception { + if (readInProgress) { + return; + } + + ChannelPipeline pipeline = pipeline(); + Queue inboundBuffer = this.inboundBuffer; + if (inboundBuffer.isEmpty()) { + readInProgress = true; + return; + } + + final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get(); + final Integer stackDepth = threadLocals.localChannelReaderStackDepth(); + if (stackDepth < MAX_READER_STACK_DEPTH) { + threadLocals.setLocalChannelReaderStackDepth(stackDepth + 1); + try { + for (;;) { + Object received = inboundBuffer.poll(); + if (received == null) { + break; + } + pipeline.fireChannelRead(received); + } + pipeline.fireChannelReadComplete(); + } finally { + threadLocals.setLocalChannelReaderStackDepth(stackDepth); + } + } else { + eventLoop().execute(readTask); + } + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + if (state < 2) { + throw new NotYetConnectedException(); + } + if (state > 2) { + throw new ClosedChannelException(); + } + + final LocalChannel peer = this.peer; + final ChannelPipeline peerPipeline = peer.pipeline(); + final EventLoop peerLoop = peer.eventLoop(); + + if (peerLoop == eventLoop()) { + for (;;) { + Object msg = in.current(); + if (msg == null) { + break; + } + peer.inboundBuffer.add(msg); + ReferenceCountUtil.retain(msg); + in.remove(); + } + finishPeerRead(peer, peerPipeline); + } else { + // Use a copy because the original msgs will be recycled by AbstractChannel. + final Object[] msgsCopy = new Object[in.size()]; + for (int i = 0; i < msgsCopy.length; i ++) { + msgsCopy[i] = ReferenceCountUtil.retain(in.current()); + in.remove(); + } + + peerLoop.execute(new Runnable() { + @Override + public void run() { + Collections.addAll(peer.inboundBuffer, msgsCopy); + finishPeerRead(peer, peerPipeline); + } + }); + } + } + + private static void finishPeerRead(LocalChannel peer, ChannelPipeline peerPipeline) { + if (peer.readInProgress) { + peer.readInProgress = false; + for (;;) { + Object received = peer.inboundBuffer.poll(); + if (received == null) { + break; + } + peerPipeline.fireChannelRead(received); + } + peerPipeline.fireChannelReadComplete(); + } + } + + private class LocalUnsafe extends AbstractUnsafe { + + @Override + public void connect(final SocketAddress remoteAddress, + SocketAddress localAddress, final ChannelPromise promise) { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + if (state == 2) { + Exception cause = new AlreadyConnectedException(); + safeSetFailure(promise, cause); + pipeline().fireExceptionCaught(cause); + return; + } + + if (connectPromise != null) { + throw new ConnectionPendingException(); + } + + connectPromise = promise; + + if (state != 1) { + // Not bound yet and no localAddress specified - get one. + if (localAddress == null) { + localAddress = new LocalAddress(LocalChannel.this); + } + } + + if (localAddress != null) { + try { + doBind(localAddress); + } catch (Throwable t) { + safeSetFailure(promise, t); + close(voidPromise()); + return; + } + } + + Channel boundChannel = LocalChannelRegistry.get(remoteAddress); + if (!(boundChannel instanceof LocalServerChannel)) { + Exception cause = new ChannelException("connection refused"); + safeSetFailure(promise, cause); + close(voidPromise()); + return; + } + + LocalServerChannel serverChannel = (LocalServerChannel) boundChannel; + peer = serverChannel.serve(LocalChannel.this); + } + } +} diff --git a/common/src/common/net/channel/local/LocalChannelRegistry.java b/common/src/common/net/channel/local/LocalChannelRegistry.java new file mode 100644 index 0000000..6af42eb --- /dev/null +++ b/common/src/common/net/channel/local/LocalChannelRegistry.java @@ -0,0 +1,62 @@ +/* + * 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 common.net.channel.local; + +import java.net.SocketAddress; +import java.util.concurrent.ConcurrentMap; + +import common.net.channel.Channel; +import common.net.channel.ChannelException; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; + +final class LocalChannelRegistry { + + private static final ConcurrentMap boundChannels = PlatformDependent.newConcurrentHashMap(); + + static LocalAddress register( + Channel channel, LocalAddress oldLocalAddress, SocketAddress localAddress) { + if (oldLocalAddress != null) { + throw new ChannelException("already bound"); + } + if (!(localAddress instanceof LocalAddress)) { + throw new ChannelException("unsupported address type: " + StringUtil.simpleClassName(localAddress)); + } + + LocalAddress addr = (LocalAddress) localAddress; + if (LocalAddress.ANY.equals(addr)) { + addr = new LocalAddress(channel); + } + + Channel boundChannel = boundChannels.putIfAbsent(addr, channel); + if (boundChannel != null) { + throw new ChannelException("address already in use by: " + boundChannel); + } + return addr; + } + + static Channel get(SocketAddress localAddress) { + return boundChannels.get(localAddress); + } + + static void unregister(LocalAddress localAddress) { + boundChannels.remove(localAddress); + } + + private LocalChannelRegistry() { + // Unused + } +} diff --git a/common/src/common/net/channel/local/LocalEventLoop.java b/common/src/common/net/channel/local/LocalEventLoop.java new file mode 100644 index 0000000..4bf1b89 --- /dev/null +++ b/common/src/common/net/channel/local/LocalEventLoop.java @@ -0,0 +1,42 @@ +/* + * 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 common.net.channel.local; + +import java.util.concurrent.ThreadFactory; + +import common.net.channel.SingleThreadEventLoop; + +final class LocalEventLoop extends SingleThreadEventLoop { + + LocalEventLoop(LocalEventLoopGroup parent, ThreadFactory threadFactory) { + super(parent, threadFactory, true); + } + + @Override + protected void run() { + for (;;) { + Runnable task = takeTask(); + if (task != null) { + task.run(); + updateLastExecutionTime(); + } + + if (confirmShutdown()) { + break; + } + } + } +} diff --git a/common/src/common/net/channel/local/LocalEventLoopGroup.java b/common/src/common/net/channel/local/LocalEventLoopGroup.java new file mode 100644 index 0000000..7f58c98 --- /dev/null +++ b/common/src/common/net/channel/local/LocalEventLoopGroup.java @@ -0,0 +1,59 @@ +/* + * 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 common.net.channel.local; + +import java.util.concurrent.ThreadFactory; + +import common.net.channel.MultithreadEventLoopGroup; +import common.net.util.concurrent.EventExecutor; + +/** + * {@link MultithreadEventLoopGroup} which must be used for the local transport. + */ +public class LocalEventLoopGroup extends MultithreadEventLoopGroup { + + /** + * Create a new instance with the default number of threads. + */ + public LocalEventLoopGroup() { + this(0); + } + + /** + * Create a new instance + * + * @param nThreads the number of threads to use + */ + public LocalEventLoopGroup(int nThreads) { + this(nThreads, null); + } + + /** + * Create a new instance + * + * @param nThreads the number of threads to use + * @param threadFactory the {@link ThreadFactory} or {@code null} to use the default + */ + public LocalEventLoopGroup(int nThreads, ThreadFactory threadFactory) { + super(nThreads, threadFactory); + } + + @Override + protected EventExecutor newChild( + ThreadFactory threadFactory, Object... args) throws Exception { + return new LocalEventLoop(this, threadFactory); + } +} diff --git a/common/src/common/net/channel/local/LocalServerChannel.java b/common/src/common/net/channel/local/LocalServerChannel.java new file mode 100644 index 0000000..557f526 --- /dev/null +++ b/common/src/common/net/channel/local/LocalServerChannel.java @@ -0,0 +1,164 @@ +/* + * 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 common.net.channel.local; + +import java.net.SocketAddress; +import java.util.ArrayDeque; +import java.util.Queue; + +import common.net.channel.AbstractServerChannel; +import common.net.channel.ChannelConfig; +import common.net.channel.ChannelPipeline; +import common.net.channel.DefaultChannelConfig; +import common.net.channel.EventLoop; +import common.net.channel.SingleThreadEventLoop; +import common.net.util.concurrent.SingleThreadEventExecutor; + +/** + * A {@link ServerChannel} for the local transport which allows in VM communication. + */ +public class LocalServerChannel extends AbstractServerChannel { + + private final ChannelConfig config = new DefaultChannelConfig(this); + private final Queue inboundBuffer = new ArrayDeque(); + private final Runnable shutdownHook = new Runnable() { + @Override + public void run() { + unsafe().close(unsafe().voidPromise()); + } + }; + + private volatile int state; // 0 - open, 1 - active, 2 - closed + private volatile LocalAddress localAddress; + private volatile boolean acceptInProgress; + + @Override + public ChannelConfig config() { + return config; + } + + @Override + public LocalAddress localAddress() { + return (LocalAddress) super.localAddress(); + } + + @Override + public LocalAddress remoteAddress() { + return (LocalAddress) super.remoteAddress(); + } + + @Override + public boolean isOpen() { + return state < 2; + } + + @Override + public boolean isActive() { + return state == 1; + } + + @Override + protected boolean isCompatible(EventLoop loop) { + return loop instanceof SingleThreadEventLoop; + } + + @Override + protected SocketAddress localAddress0() { + return localAddress; + } + + @Override + protected void doRegister() throws Exception { + ((SingleThreadEventExecutor) eventLoop()).addShutdownHook(shutdownHook); + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + this.localAddress = LocalChannelRegistry.register(this, this.localAddress, localAddress); + state = 1; + } + + @Override + protected void doClose() throws Exception { + if (state <= 1) { + // Update all internal state before the closeFuture is notified. + if (localAddress != null) { + LocalChannelRegistry.unregister(localAddress); + localAddress = null; + } + state = 2; + } + } + + @Override + protected void doDeregister() throws Exception { + ((SingleThreadEventExecutor) eventLoop()).removeShutdownHook(shutdownHook); + } + + @Override + protected void doBeginRead() throws Exception { + if (acceptInProgress) { + return; + } + + Queue inboundBuffer = this.inboundBuffer; + if (inboundBuffer.isEmpty()) { + acceptInProgress = true; + return; + } + + ChannelPipeline pipeline = pipeline(); + for (;;) { + Object m = inboundBuffer.poll(); + if (m == null) { + break; + } + pipeline.fireChannelRead(m); + } + pipeline.fireChannelReadComplete(); + } + + LocalChannel serve(final LocalChannel peer) { + final LocalChannel child = new LocalChannel(this, peer); + if (eventLoop().inEventLoop()) { + serve0(child); + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + serve0(child); + } + }); + } + return child; + } + + private void serve0(final LocalChannel child) { + inboundBuffer.add(child); + if (acceptInProgress) { + acceptInProgress = false; + ChannelPipeline pipeline = pipeline(); + for (;;) { + Object m = inboundBuffer.poll(); + if (m == null) { + break; + } + pipeline.fireChannelRead(m); + } + pipeline.fireChannelReadComplete(); + } + } +} diff --git a/common/src/common/net/channel/nio/AbstractNioByteChannel.java b/common/src/common/net/channel/nio/AbstractNioByteChannel.java new file mode 100644 index 0000000..ac4cc89 --- /dev/null +++ b/common/src/common/net/channel/nio/AbstractNioByteChannel.java @@ -0,0 +1,347 @@ +/* + * 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 common.net.channel.nio; + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; + +import common.net.buffer.ByteBuf; +import common.net.buffer.ByteBufAllocator; +import common.net.channel.Channel; +import common.net.channel.ChannelConfig; +import common.net.channel.ChannelOption; +import common.net.channel.ChannelOutboundBuffer; +import common.net.channel.ChannelPipeline; +import common.net.channel.RecvByteBufAllocator; +import common.net.channel.socket.ChannelInputShutdownEvent; +import common.net.util.internal.StringUtil; + +/** + * {@link AbstractNioChannel} base class for {@link Channel}s that operate on bytes. + */ +public abstract class AbstractNioByteChannel extends AbstractNioChannel { + + private static final String EXPECTED_TYPES = + " (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ')'; // ", " + +// StringUtil.simpleClassName(FileRegion.class) + ')'; + + private Runnable flushTask; + + /** + * Create a new instance + * + * @param parent the parent {@link Channel} by which this instance was created. May be {@code null} + * @param ch the underlying {@link SelectableChannel} on which it operates + */ + protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) { + super(parent, ch, SelectionKey.OP_READ); + } + + @Override + protected AbstractNioUnsafe newUnsafe() { + return new NioByteUnsafe(); + } + + private final class NioByteUnsafe extends AbstractNioUnsafe { + private RecvByteBufAllocator.Handle allocHandle; + + private void closeOnRead(ChannelPipeline pipeline) { + SelectionKey key = selectionKey(); + setInputShutdown(); + if (isOpen()) { + if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) { + key.interestOps(key.interestOps() & ~readInterestOp); + pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE); + } else { + close(voidPromise()); + } + } + } + + private void handleReadException(ChannelPipeline pipeline, + ByteBuf byteBuf, Throwable cause, boolean close) { + if (byteBuf != null) { + if (byteBuf.isReadable()) { + setReadPending(false); + pipeline.fireChannelRead(byteBuf); + } else { + byteBuf.release(); + } + } + pipeline.fireChannelReadComplete(); + pipeline.fireExceptionCaught(cause); + if (close || cause instanceof IOException) { + closeOnRead(pipeline); + } + } + + @Override + public void read() { + final ChannelConfig config = config(); + if (!config.isAutoRead() && !isReadPending()) { + // ChannelConfig.setAutoRead(false) was called in the meantime + removeReadOp(); + return; + } + + final ChannelPipeline pipeline = pipeline(); + final ByteBufAllocator allocator = config.getAllocator(); + final int maxMessagesPerRead = config.getMaxMessagesPerRead(); + RecvByteBufAllocator.Handle allocHandle = this.allocHandle; + if (allocHandle == null) { + this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle(); + } + + ByteBuf byteBuf = null; + int messages = 0; + boolean close = false; + try { + int totalReadAmount = 0; + boolean readPendingReset = false; + do { + byteBuf = allocHandle.allocate(allocator); + int writable = byteBuf.writableBytes(); + int localReadAmount = doReadBytes(byteBuf); + if (localReadAmount <= 0) { + // not was read release the buffer + byteBuf.release(); + close = localReadAmount < 0; + break; + } + if (!readPendingReset) { + readPendingReset = true; + setReadPending(false); + } + pipeline.fireChannelRead(byteBuf); + byteBuf = null; + + if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) { + // Avoid overflow. + totalReadAmount = Integer.MAX_VALUE; + break; + } + + totalReadAmount += localReadAmount; + + // stop reading + if (!config.isAutoRead()) { + break; + } + + if (localReadAmount < writable) { + // Read less than what the buffer can hold, + // which might mean we drained the recv buffer completely. + break; + } + } while (++ messages < maxMessagesPerRead); + + pipeline.fireChannelReadComplete(); + allocHandle.record(totalReadAmount); + + if (close) { + closeOnRead(pipeline); + close = false; + } + } catch (Throwable t) { + handleReadException(pipeline, byteBuf, t, close); + } finally { + // Check if there is a readPending which was not processed yet. + // This could be for two reasons: + // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method + // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method + // + // See https://github.com/netty/netty/issues/2254 + if (!config.isAutoRead() && !isReadPending()) { + removeReadOp(); + } + } + } + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + int writeSpinCount = -1; + + for (;;) { + Object msg = in.current(); + if (msg == null) { + // Wrote all messages. + clearOpWrite(); + break; + } + + if (msg instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) msg; + int readableBytes = buf.readableBytes(); + if (readableBytes == 0) { + in.remove(); + continue; + } + + boolean setOpWrite = false; + boolean done = false; + long flushedAmount = 0; + if (writeSpinCount == -1) { + writeSpinCount = config().getWriteSpinCount(); + } + for (int i = writeSpinCount - 1; i >= 0; i --) { + int localFlushedAmount = doWriteBytes(buf); + if (localFlushedAmount == 0) { + setOpWrite = true; + break; + } + + flushedAmount += localFlushedAmount; + if (!buf.isReadable()) { + done = true; + break; + } + } + + in.progress(flushedAmount); + + if (done) { + in.remove(); + } else { + incompleteWrite(setOpWrite); + break; + } + } +// else if (msg instanceof FileRegion) { +// FileRegion region = (FileRegion) msg; +// boolean setOpWrite = false; +// boolean done = false; +// long flushedAmount = 0; +// if (writeSpinCount == -1) { +// writeSpinCount = config().getWriteSpinCount(); +// } +// for (int i = writeSpinCount - 1; i >= 0; i --) { +// long localFlushedAmount = doWriteFileRegion(region); +// if (localFlushedAmount == 0) { +// setOpWrite = true; +// break; +// } +// +// flushedAmount += localFlushedAmount; +// if (region.transfered() >= region.count()) { +// done = true; +// break; +// } +// } +// +// in.progress(flushedAmount); +// +// if (done) { +// in.remove(); +// } else { +// incompleteWrite(setOpWrite); +// break; +// } +// } + else { + // Should not reach here. + throw new Error(); + } + } + } + + @Override + protected final Object filterOutboundMessage(Object msg) { + if (msg instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) msg; + if (buf.isDirect()) { + return msg; + } + + return newDirectBuffer(buf); + } + +// if (msg instanceof FileRegion) { +// return msg; +// } + + throw new UnsupportedOperationException( + "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES); + } + + protected final void incompleteWrite(boolean setOpWrite) { + // Did not write completely. + if (setOpWrite) { + setOpWrite(); + } else { + // Schedule flush again later so other tasks can be picked up in the meantime + Runnable flushTask = this.flushTask; + if (flushTask == null) { + flushTask = this.flushTask = new Runnable() { + @Override + public void run() { + flush(); + } + }; + } + eventLoop().execute(flushTask); + } + } + + /** + * Write a {@link FileRegion} + * + * @param region the {@link FileRegion} from which the bytes should be written + * @return amount the amount of written bytes + */ +// protected abstract long doWriteFileRegion(FileRegion region) throws Exception; + + /** + * Read bytes into the given {@link ByteBuf} and return the amount. + */ + protected abstract int doReadBytes(ByteBuf buf) throws Exception; + + /** + * Write bytes form the given {@link ByteBuf} to the underlying {@link java.nio.channels.Channel}. + * @param buf the {@link ByteBuf} from which the bytes should be written + * @return amount the amount of written bytes + */ + protected abstract int doWriteBytes(ByteBuf buf) throws Exception; + + protected final void setOpWrite() { + final SelectionKey key = selectionKey(); + // Check first if the key is still valid as it may be canceled as part of the deregistration + // from the EventLoop + // See https://github.com/netty/netty/issues/2104 + if (!key.isValid()) { + return; + } + final int interestOps = key.interestOps(); + if ((interestOps & SelectionKey.OP_WRITE) == 0) { + key.interestOps(interestOps | SelectionKey.OP_WRITE); + } + } + + protected final void clearOpWrite() { + final SelectionKey key = selectionKey(); + // Check first if the key is still valid as it may be canceled as part of the deregistration + // from the EventLoop + // See https://github.com/netty/netty/issues/2104 + if (!key.isValid()) { + return; + } + final int interestOps = key.interestOps(); + if ((interestOps & SelectionKey.OP_WRITE) != 0) { + key.interestOps(interestOps & ~SelectionKey.OP_WRITE); + } + } +} diff --git a/common/src/common/net/channel/nio/AbstractNioChannel.java b/common/src/common/net/channel/nio/AbstractNioChannel.java new file mode 100644 index 0000000..214f1de --- /dev/null +++ b/common/src/common/net/channel/nio/AbstractNioChannel.java @@ -0,0 +1,460 @@ +/* + * 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 common.net.channel.nio; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketAddress; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import common.net.buffer.ByteBuf; +import common.net.buffer.ByteBufAllocator; +import common.net.buffer.ByteBufUtil; +import common.net.buffer.Unpooled; +import common.net.channel.AbstractChannel; +import common.net.channel.Channel; +import common.net.channel.ChannelException; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelFutureListener; +import common.net.channel.ChannelPromise; +import common.net.channel.ConnectTimeoutException; +import common.net.channel.EventLoop; +import common.net.util.ReferenceCountUtil; +import common.net.util.ReferenceCounted; +import common.net.util.internal.OneTimeTask; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Abstract base class for {@link Channel} implementations which use a Selector based approach. + */ +public abstract class AbstractNioChannel extends AbstractChannel { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(AbstractNioChannel.class); + + private final SelectableChannel ch; + protected final int readInterestOp; + volatile SelectionKey selectionKey; + private volatile boolean inputShutdown; + private volatile boolean readPending; + + /** + * The future of the current connection attempt. If not null, subsequent + * connection attempts will fail. + */ + private ChannelPromise connectPromise; + private ScheduledFuture connectTimeoutFuture; + private SocketAddress requestedRemoteAddress; + + /** + * Create a new instance + * + * @param parent the parent {@link Channel} by which this instance was created. May be {@code null} + * @param ch the underlying {@link SelectableChannel} on which it operates + * @param readInterestOp the ops to set to receive data from the {@link SelectableChannel} + */ + protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { + super(parent); + this.ch = ch; + this.readInterestOp = readInterestOp; + try { + ch.configureBlocking(false); + } catch (IOException e) { + try { + ch.close(); + } catch (IOException e2) { + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", e2); + } + } + + throw new ChannelException("Failed to enter non-blocking mode.", e); + } + } + + @Override + public boolean isOpen() { + return ch.isOpen(); + } + + @Override + public NioUnsafe unsafe() { + return (NioUnsafe) super.unsafe(); + } + + protected SelectableChannel javaChannel() { + return ch; + } + + @Override + public NioEventLoop eventLoop() { + return (NioEventLoop) super.eventLoop(); + } + + /** + * Return the current {@link SelectionKey} + */ + protected SelectionKey selectionKey() { + assert selectionKey != null; + return selectionKey; + } + + protected boolean isReadPending() { + return readPending; + } + + protected void setReadPending(boolean readPending) { + this.readPending = readPending; + } + + /** + * Return {@code true} if the input of this {@link Channel} is shutdown + */ + protected boolean isInputShutdown() { + return inputShutdown; + } + + /** + * Shutdown the input of this {@link Channel}. + */ + void setInputShutdown() { + inputShutdown = true; + } + + /** + * Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel} + */ + public interface NioUnsafe extends Unsafe { + /** + * Return underlying {@link SelectableChannel} + */ + SelectableChannel ch(); + + /** + * Finish connect + */ + void finishConnect(); + + /** + * Read from underlying {@link SelectableChannel} + */ + void read(); + + void forceFlush(); + } + + protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe { + + protected final void removeReadOp() { + SelectionKey key = selectionKey(); + // Check first if the key is still valid as it may be canceled as part of the deregistration + // from the EventLoop + // See https://github.com/netty/netty/issues/2104 + if (!key.isValid()) { + return; + } + int interestOps = key.interestOps(); + if ((interestOps & readInterestOp) != 0) { + // only remove readInterestOp if needed + key.interestOps(interestOps & ~readInterestOp); + } + } + + @Override + public final SelectableChannel ch() { + return javaChannel(); + } + + @Override + public final void connect( + final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + try { + if (connectPromise != null) { + throw new IllegalStateException("connection attempt already made"); + } + + boolean wasActive = isActive(); + if (doConnect(remoteAddress, localAddress)) { + fulfillConnectPromise(promise, wasActive); + } else { + connectPromise = promise; + requestedRemoteAddress = remoteAddress; + + // Schedule connect timeout. + int connectTimeoutMillis = config().getConnectTimeoutMillis(); + if (connectTimeoutMillis > 0) { + connectTimeoutFuture = eventLoop().schedule(new OneTimeTask() { + @Override + public void run() { + ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise; + ConnectTimeoutException cause = + new ConnectTimeoutException("connection timed out: " + remoteAddress); + if (connectPromise != null && connectPromise.tryFailure(cause)) { + close(voidPromise()); + } + } + }, connectTimeoutMillis, TimeUnit.MILLISECONDS); + } + + promise.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isCancelled()) { + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + close(voidPromise()); + } + } + }); + } + } catch (Throwable t) { + if (t instanceof ConnectException) { + Throwable newT = new ConnectException(t.getMessage() + ": " + remoteAddress); + newT.setStackTrace(t.getStackTrace()); + t = newT; + } + promise.tryFailure(t); + closeIfClosed(); + } + } + + private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { + if (promise == null) { + // Closed via cancellation and the promise has been notified already. + return; + } + + // trySuccess() will return false if a user cancelled the connection attempt. + boolean promiseSet = promise.trySuccess(); + + // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, + // because what happened is what happened. + if (!wasActive && isActive()) { + pipeline().fireChannelActive(); + } + + // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive(). + if (!promiseSet) { + close(voidPromise()); + } + } + + private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) { + if (promise == null) { + // Closed via cancellation and the promise has been notified already. + return; + } + + // Use tryFailure() instead of setFailure() to avoid the race against cancel(). + promise.tryFailure(cause); + closeIfClosed(); + } + + @Override + public final void finishConnect() { + // Note this method is invoked by the event loop only if the connection attempt was + // neither cancelled nor timed out. + + assert eventLoop().inEventLoop(); + + try { + boolean wasActive = isActive(); + doFinishConnect(); + fulfillConnectPromise(connectPromise, wasActive); + } catch (Throwable t) { + if (t instanceof ConnectException) { + Throwable newT = new ConnectException(t.getMessage() + ": " + requestedRemoteAddress); + newT.setStackTrace(t.getStackTrace()); + t = newT; + } + + fulfillConnectPromise(connectPromise, t); + } finally { + // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used + // See https://github.com/netty/netty/issues/1770 + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + } + } + + @Override + protected final void flush0() { + // Flush immediately only when there's no pending flush. + // If there's a pending flush operation, event loop will call forceFlush() later, + // and thus there's no need to call it now. + if (isFlushPending()) { + return; + } + super.flush0(); + } + + @Override + public final void forceFlush() { + // directly call super.flush0() to force a flush now + super.flush0(); + } + + private boolean isFlushPending() { + SelectionKey selectionKey = selectionKey(); + return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0; + } + } + + @Override + protected boolean isCompatible(EventLoop loop) { + return loop instanceof NioEventLoop; + } + + @Override + protected void doRegister() throws Exception { + boolean selected = false; + for (;;) { + try { + selectionKey = javaChannel().register(eventLoop().selector, 0, this); + return; + } catch (CancelledKeyException e) { + if (!selected) { + // Force the Selector to select now as the "canceled" SelectionKey may still be + // cached and not removed because no Select.select(..) operation was called yet. + eventLoop().selectNow(); + selected = true; + } else { + // We forced a select operation on the selector before but the SelectionKey is still cached + // for whatever reason. JDK bug ? + throw e; + } + } + } + } + + @Override + protected void doDeregister() throws Exception { + eventLoop().cancel(selectionKey()); + } + + @Override + protected void doBeginRead() throws Exception { + // Channel.read() or ChannelHandlerContext.read() was called + if (inputShutdown) { + return; + } + + final SelectionKey selectionKey = this.selectionKey; + if (!selectionKey.isValid()) { + return; + } + + readPending = true; + + final int interestOps = selectionKey.interestOps(); + if ((interestOps & readInterestOp) == 0) { + selectionKey.interestOps(interestOps | readInterestOp); + } + } + + /** + * Connect to the remote peer + */ + protected abstract boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception; + + /** + * Finish the connect + */ + protected abstract void doFinishConnect() throws Exception; + + /** + * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one. + * Note that this method does not create an off-heap copy if the allocation / deallocation cost is too high, + * but just returns the original {@link ByteBuf}.. + */ + protected final ByteBuf newDirectBuffer(ByteBuf buf) { + final int readableBytes = buf.readableBytes(); + if (readableBytes == 0) { + ReferenceCountUtil.safeRelease(buf); + return Unpooled.EMPTY_BUFFER; + } + + final ByteBufAllocator alloc = alloc(); + if (alloc.isDirectBufferPooled()) { + ByteBuf directBuf = alloc.directBuffer(readableBytes); + directBuf.writeBytes(buf, buf.readerIndex(), readableBytes); + ReferenceCountUtil.safeRelease(buf); + return directBuf; + } + + final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer(); + if (directBuf != null) { + directBuf.writeBytes(buf, buf.readerIndex(), readableBytes); + ReferenceCountUtil.safeRelease(buf); + return directBuf; + } + + // Allocating and deallocating an unpooled direct buffer is very expensive; give up. + return buf; + } + + /** + * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder. + * The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by + * this method. Note that this method does not create an off-heap copy if the allocation / deallocation cost is + * too high, but just returns the original {@link ByteBuf}.. + */ + protected final ByteBuf newDirectBuffer(ReferenceCounted holder, ByteBuf buf) { + final int readableBytes = buf.readableBytes(); + if (readableBytes == 0) { + ReferenceCountUtil.safeRelease(holder); + return Unpooled.EMPTY_BUFFER; + } + + final ByteBufAllocator alloc = alloc(); + if (alloc.isDirectBufferPooled()) { + ByteBuf directBuf = alloc.directBuffer(readableBytes); + directBuf.writeBytes(buf, buf.readerIndex(), readableBytes); + ReferenceCountUtil.safeRelease(holder); + return directBuf; + } + + final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer(); + if (directBuf != null) { + directBuf.writeBytes(buf, buf.readerIndex(), readableBytes); + ReferenceCountUtil.safeRelease(holder); + return directBuf; + } + + // Allocating and deallocating an unpooled direct buffer is very expensive; give up. + if (holder != buf) { + // Ensure to call holder.release() to give the holder a chance to release other resources than its content. + buf.retain(); + ReferenceCountUtil.safeRelease(holder); + } + + return buf; + } +} diff --git a/common/src/common/net/channel/nio/AbstractNioMessageChannel.java b/common/src/common/net/channel/nio/AbstractNioMessageChannel.java new file mode 100644 index 0000000..3153d17 --- /dev/null +++ b/common/src/common/net/channel/nio/AbstractNioMessageChannel.java @@ -0,0 +1,187 @@ +/* + * 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 common.net.channel.nio; + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.util.ArrayList; +import java.util.List; + +import common.net.channel.Channel; +import common.net.channel.ChannelConfig; +import common.net.channel.ChannelOutboundBuffer; +import common.net.channel.ChannelPipeline; +import common.net.channel.ServerChannel; + +/** + * {@link AbstractNioChannel} base class for {@link Channel}s that operate on messages. + */ +public abstract class AbstractNioMessageChannel extends AbstractNioChannel { + + /** + * @see {@link AbstractNioChannel#AbstractNioChannel(Channel, SelectableChannel, int)} + */ + protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) { + super(parent, ch, readInterestOp); + } + + @Override + protected AbstractNioUnsafe newUnsafe() { + return new NioMessageUnsafe(); + } + + private final class NioMessageUnsafe extends AbstractNioUnsafe { + + private final List readBuf = new ArrayList(); + + @Override + public void read() { + assert eventLoop().inEventLoop(); + final ChannelConfig config = config(); + if (!config.isAutoRead() && !isReadPending()) { + // ChannelConfig.setAutoRead(false) was called in the meantime + removeReadOp(); + return; + } + + final int maxMessagesPerRead = config.getMaxMessagesPerRead(); + final ChannelPipeline pipeline = pipeline(); + boolean closed = false; + Throwable exception = null; + try { + try { + for (;;) { + int localRead = doReadMessages(readBuf); + if (localRead == 0) { + break; + } + if (localRead < 0) { + closed = true; + break; + } + + // stop reading and remove op + if (!config.isAutoRead()) { + break; + } + + if (readBuf.size() >= maxMessagesPerRead) { + break; + } + } + } catch (Throwable t) { + exception = t; + } + setReadPending(false); + int size = readBuf.size(); + for (int i = 0; i < size; i ++) { + pipeline.fireChannelRead(readBuf.get(i)); + } + + readBuf.clear(); + pipeline.fireChannelReadComplete(); + + if (exception != null) { + if (exception instanceof IOException) { + // ServerChannel should not be closed even on IOException because it can often continue + // accepting incoming connections. (e.g. too many open files) + closed = !(AbstractNioMessageChannel.this instanceof ServerChannel); + } + + pipeline.fireExceptionCaught(exception); + } + + if (closed) { + if (isOpen()) { + close(voidPromise()); + } + } + } finally { + // Check if there is a readPending which was not processed yet. + // This could be for two reasons: + // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method + // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method + // + // See https://github.com/netty/netty/issues/2254 + if (!config.isAutoRead() && !isReadPending()) { + removeReadOp(); + } + } + } + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + final SelectionKey key = selectionKey(); + final int interestOps = key.interestOps(); + + for (;;) { + Object msg = in.current(); + if (msg == null) { + // Wrote all messages. + if ((interestOps & SelectionKey.OP_WRITE) != 0) { + key.interestOps(interestOps & ~SelectionKey.OP_WRITE); + } + break; + } + try { + boolean done = false; + for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) { + if (doWriteMessage(msg, in)) { + done = true; + break; + } + } + + if (done) { + in.remove(); + } else { + // Did not write all messages. + if ((interestOps & SelectionKey.OP_WRITE) == 0) { + key.interestOps(interestOps | SelectionKey.OP_WRITE); + } + break; + } + } catch (IOException e) { + if (continueOnWriteError()) { + in.remove(e); + } else { + throw e; + } + } + } + } + + /** + * Returns {@code true} if we should continue the write loop on a write error. + */ + protected boolean continueOnWriteError() { + return false; + } + + /** + * Read messages into the given array and return the amount which was read. + */ + protected abstract int doReadMessages(List buf) throws Exception; + + /** + * Write a message to the underlying {@link java.nio.channels.Channel}. + * + * @return {@code true} if and only if the message has been written + */ + protected abstract boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception; +} diff --git a/common/src/common/net/channel/nio/NioEventLoop.java b/common/src/common/net/channel/nio/NioEventLoop.java new file mode 100644 index 0000000..a2b75f9 --- /dev/null +++ b/common/src/common/net/channel/nio/NioEventLoop.java @@ -0,0 +1,691 @@ +/* + * 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 common.net.channel.nio; + + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.SelectorProvider; +import java.util.ArrayList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import common.net.channel.ChannelException; +import common.net.channel.EventLoopException; +import common.net.channel.SingleThreadEventLoop; +import common.net.channel.nio.AbstractNioChannel.NioUnsafe; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.SystemPropertyUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * {@link SingleThreadEventLoop} implementation which register the {@link Channel}'s to a + * {@link Selector} and so does the multi-plexing of these in the event loop. + * + */ +public final class NioEventLoop extends SingleThreadEventLoop { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioEventLoop.class); + + private static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization. + + private static final boolean DISABLE_KEYSET_OPTIMIZATION = + SystemPropertyUtil.getBoolean("game.net.noKeySetOptimization", false); + + private static final int MIN_PREMATURE_SELECTOR_RETURNS = 3; + private static final int SELECTOR_AUTO_REBUILD_THRESHOLD; + + // 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 = SystemPropertyUtil.get(key); + if (buglevel == null) { + System.setProperty(key, ""); + } + } catch (SecurityException e) { + if (logger.isDebugEnabled()) { + logger.debug("Unable to get/set System Property: {}", key, e); + } + } + + int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("game.net.selectorAutoRebuildThreshold", 512); + if (selectorAutoRebuildThreshold < MIN_PREMATURE_SELECTOR_RETURNS) { + selectorAutoRebuildThreshold = 0; + } + + SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold; + + if (logger.isDebugEnabled()) { + logger.debug("-Dgame.net.noKeySetOptimization: {}", DISABLE_KEYSET_OPTIMIZATION); + logger.debug("-Dgame.net.selectorAutoRebuildThreshold: {}", SELECTOR_AUTO_REBUILD_THRESHOLD); + } + } + + /** + * The NIO {@link Selector}. + */ + Selector selector; + private SelectedSelectionKeySet selectedKeys; + + private final SelectorProvider provider; + + /** + * Boolean that controls determines if a blocked Selector.select should + * break out of its selection process. In our case we use a timeout for + * the select method and the select method will block for that time unless + * waken up. + */ + private final AtomicBoolean wakenUp = new AtomicBoolean(); + + private volatile int ioRatio = 50; + private int cancelledKeys; + private boolean needsToSelectAgain; + + NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) { + super(parent, threadFactory, false); + if (selectorProvider == null) { + throw new NullPointerException("selectorProvider"); + } + provider = selectorProvider; + selector = openSelector(); + } + + private Selector openSelector() { + final Selector selector; + try { + selector = provider.openSelector(); + } catch (IOException e) { + throw new ChannelException("failed to open a new selector", e); + } + + if (DISABLE_KEYSET_OPTIMIZATION) { + return selector; + } + + try { + SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet(); + + Class selectorImplClass = + Class.forName("sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader()); + + // Ensure the current selector implementation is what we can instrument. + if (!selectorImplClass.isAssignableFrom(selector.getClass())) { + return selector; + } + + Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys"); + Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); + + selectedKeysField.setAccessible(true); + publicSelectedKeysField.setAccessible(true); + + selectedKeysField.set(selector, selectedKeySet); + publicSelectedKeysField.set(selector, selectedKeySet); + + selectedKeys = selectedKeySet; + logger.trace("Instrumented an optimized java.util.Set into: {}", selector); + } catch (Throwable t) { + selectedKeys = null; + logger.trace("Failed to instrument an optimized java.util.Set into: {}", selector, t); + } + + return selector; + } + + @Override + protected Queue newTaskQueue() { + // This event loop never calls takeTask() + return PlatformDependent.newMpscQueue(); + } + + /** + * Registers an arbitrary {@link SelectableChannel}, not necessarily created by Netty, to the {@link Selector} + * of this event loop. Once the specified {@link SelectableChannel} is registered, the specified {@code task} will + * be executed by this event loop when the {@link SelectableChannel} is ready. + */ + public void register(final SelectableChannel ch, final int interestOps, final NioTask task) { + if (ch == null) { + throw new NullPointerException("ch"); + } + if (interestOps == 0) { + throw new IllegalArgumentException("interestOps must be non-zero."); + } + if ((interestOps & ~ch.validOps()) != 0) { + throw new IllegalArgumentException( + "invalid interestOps: " + interestOps + "(validOps: " + ch.validOps() + ')'); + } + if (task == null) { + throw new NullPointerException("task"); + } + + if (isShutdown()) { + throw new IllegalStateException("event loop shut down"); + } + + try { + ch.register(selector, interestOps, task); + } catch (Exception e) { + throw new EventLoopException("failed to register a channel", e); + } + } + + /** + * Returns the percentage of the desired amount of time spent for I/O in the event loop. + */ + public int getIoRatio() { + return ioRatio; + } + + /** + * Sets the percentage of the desired amount of time spent for I/O in the event loop. The default value is + * {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks. + */ + public void setIoRatio(int ioRatio) { + if (ioRatio <= 0 || ioRatio > 100) { + throw new IllegalArgumentException("ioRatio: " + ioRatio + " (expected: 0 < ioRatio <= 100)"); + } + this.ioRatio = ioRatio; + } + + /** + * Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work + * around the infamous epoll 100% CPU bug. + */ + public void rebuildSelector() { + if (!inEventLoop()) { + execute(new Runnable() { + @Override + public void run() { + rebuildSelector(); + } + }); + return; + } + + final Selector oldSelector = selector; + final Selector newSelector; + + if (oldSelector == null) { + return; + } + + try { + newSelector = openSelector(); + } catch (Exception e) { + logger.warn("Failed to create a new Selector.", e); + return; + } + + // Register all channels to the new Selector. + int nChannels = 0; + for (;;) { + try { + for (SelectionKey key: oldSelector.keys()) { + Object a = key.attachment(); + try { + if (!key.isValid() || key.channel().keyFor(newSelector) != null) { + continue; + } + + int interestOps = key.interestOps(); + key.cancel(); + SelectionKey newKey = key.channel().register(newSelector, interestOps, a); + if (a instanceof AbstractNioChannel) { + // Update SelectionKey + ((AbstractNioChannel) a).selectionKey = newKey; + } + nChannels ++; + } catch (Exception e) { + logger.warn("Failed to re-register a Channel to the new Selector.", e); + if (a instanceof AbstractNioChannel) { + AbstractNioChannel ch = (AbstractNioChannel) a; + ch.unsafe().close(ch.unsafe().voidPromise()); + } else { + + NioTask task = (NioTask) a; + invokeChannelUnregistered(task, key, e); + } + } + } + } catch (ConcurrentModificationException e) { + // Probably due to concurrent modification of the key set. + continue; + } + + break; + } + + selector = newSelector; + + try { + // time to close the old selector as everything else is registered to the new one + oldSelector.close(); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to close the old Selector.", t); + } + } + + logger.info("Migrated " + nChannels + " channel(s) to the new Selector."); + } + + @Override + protected void run() { + for (;;) { + boolean oldWakenUp = wakenUp.getAndSet(false); + try { + if (hasTasks()) { + selectNow(); + } else { + select(oldWakenUp); + + // '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; + needsToSelectAgain = false; + final int ioRatio = this.ioRatio; + if (ioRatio == 100) { + processSelectedKeys(); + runAllTasks(); + } else { + final long ioStartTime = System.nanoTime(); + + processSelectedKeys(); + + final long ioTime = System.nanoTime() - ioStartTime; + runAllTasks(ioTime * (100 - ioRatio) / ioRatio); + } + + if (isShuttingDown()) { + closeAll(); + if (confirmShutdown()) { + break; + } + } + } 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 processSelectedKeys() { + if (selectedKeys != null) { + processSelectedKeysOptimized(selectedKeys.flip()); + } else { + processSelectedKeysPlain(selector.selectedKeys()); + } + } + + @Override + protected void cleanup() { + try { + selector.close(); + } catch (IOException e) { + logger.warn("Failed to close a selector.", e); + } + } + + void cancel(SelectionKey key) { + key.cancel(); + cancelledKeys ++; + if (cancelledKeys >= CLEANUP_INTERVAL) { + cancelledKeys = 0; + needsToSelectAgain = true; + } + } + + @Override + protected Runnable pollTask() { + Runnable task = super.pollTask(); + if (needsToSelectAgain) { + selectAgain(); + } + return task; + } + + private void processSelectedKeysPlain(Set selectedKeys) { + // check if the set is empty and if so just return to not create garbage by + // creating a new Iterator every time even if there is nothing to process. + // See https://github.com/netty/netty/issues/597 + if (selectedKeys.isEmpty()) { + return; + } + + Iterator i = selectedKeys.iterator(); + for (;;) { + final SelectionKey k = i.next(); + final Object a = k.attachment(); + i.remove(); + + if (a instanceof AbstractNioChannel) { + processSelectedKey(k, (AbstractNioChannel) a); + } else { + + NioTask task = (NioTask) a; + processSelectedKey(k, task); + } + + if (!i.hasNext()) { + break; + } + + if (needsToSelectAgain) { + selectAgain(); + selectedKeys = selector.selectedKeys(); + + // Create the iterator again to avoid ConcurrentModificationException + if (selectedKeys.isEmpty()) { + break; + } else { + i = selectedKeys.iterator(); + } + } + } + } + + private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) { + for (int i = 0;; i ++) { + final SelectionKey k = selectedKeys[i]; + if (k == null) { + break; + } + // null out entry in the array to allow to have it GC'ed once the Channel close + // See https://github.com/netty/netty/issues/2363 + selectedKeys[i] = null; + + final Object a = k.attachment(); + + if (a instanceof AbstractNioChannel) { + processSelectedKey(k, (AbstractNioChannel) a); + } else { + + NioTask task = (NioTask) a; + processSelectedKey(k, task); + } + + if (needsToSelectAgain) { + // null out entries in the array to allow to have it GC'ed once the Channel close + // See https://github.com/netty/netty/issues/2363 + for (;;) { + if (selectedKeys[i] == null) { + break; + } + selectedKeys[i] = null; + i++; + } + + selectAgain(); + // Need to flip the optimized selectedKeys to get the right reference to the array + // and reset the index to -1 which will then set to 0 on the for loop + // to start over again. + // + // See https://github.com/netty/netty/issues/1523 + selectedKeys = this.selectedKeys.flip(); + i = -1; + } + } + } + + private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { + final NioUnsafe unsafe = ch.unsafe(); + if (!k.isValid()) { + // close the channel if the key is not valid anymore + unsafe.close(unsafe.voidPromise()); + return; + } + + try { + int readyOps = k.readyOps(); + // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead + // to a spin loop + if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { + unsafe.read(); + if (!ch.isOpen()) { + // Connection already closed - no need to handle write. + return; + } + } + if ((readyOps & SelectionKey.OP_WRITE) != 0) { + // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write + ch.unsafe().forceFlush(); + } + if ((readyOps & SelectionKey.OP_CONNECT) != 0) { + // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking + // See https://github.com/netty/netty/issues/924 + int ops = k.interestOps(); + ops &= ~SelectionKey.OP_CONNECT; + k.interestOps(ops); + + unsafe.finishConnect(); + } + } catch (CancelledKeyException ignored) { + unsafe.close(unsafe.voidPromise()); + } + } + + private static void processSelectedKey(SelectionKey k, NioTask task) { + int state = 0; + try { + task.channelReady(k.channel(), k); + state = 1; + } catch (Exception e) { + k.cancel(); + invokeChannelUnregistered(task, k, e); + state = 2; + } finally { + switch (state) { + case 0: + k.cancel(); + invokeChannelUnregistered(task, k, null); + break; + case 1: + if (!k.isValid()) { // Cancelled by channelReady() + invokeChannelUnregistered(task, k, null); + } + break; + } + } + } + + private void closeAll() { + selectAgain(); + Set keys = selector.keys(); + Collection channels = new ArrayList(keys.size()); + for (SelectionKey k: keys) { + Object a = k.attachment(); + if (a instanceof AbstractNioChannel) { + channels.add((AbstractNioChannel) a); + } else { + k.cancel(); + + NioTask task = (NioTask) a; + invokeChannelUnregistered(task, k, null); + } + } + + for (AbstractNioChannel ch: channels) { + ch.unsafe().close(ch.unsafe().voidPromise()); + } + } + + private static void invokeChannelUnregistered(NioTask task, SelectionKey k, Throwable cause) { + try { + task.channelUnregistered(k.channel(), cause); + } catch (Exception e) { + logger.warn("Unexpected exception while running NioTask.channelUnregistered()", e); + } + } + + @Override + protected void wakeup(boolean inEventLoop) { + if (!inEventLoop && wakenUp.compareAndSet(false, true)) { + selector.wakeup(); + } + } + + void selectNow() throws IOException { + try { + selector.selectNow(); + } finally { + // restore wakup state if needed + if (wakenUp.get()) { + selector.wakeup(); + } + } + } + + private void select(boolean oldWakenUp) throws IOException { + Selector selector = this.selector; + try { + int selectCnt = 0; + long currentTimeNanos = System.nanoTime(); + long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos); + for (;;) { + long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L; + if (timeoutMillis <= 0) { + if (selectCnt == 0) { + selector.selectNow(); + selectCnt = 1; + } + break; + } + + int selectedKeys = selector.select(timeoutMillis); + selectCnt ++; + + if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) { + // - Selected something, + // - waken up by user, or + // - the task queue has a pending task. + // - a scheduled task is ready for processing + break; + } + if (Thread.interrupted()) { + // Thread was interrupted so reset selected keys and break so we not run into a busy loop. + // As this is most likely a bug in the handler of the user or it's client library we will + // also log it. + // + // See https://github.com/netty/netty/issues/2426 + if (logger.isDebugEnabled()) { + logger.debug("Selector.select() returned prematurely because " + + "Thread.currentThread().interrupt() was called. Use " + + "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop."); + } + selectCnt = 1; + break; + } + + long time = System.nanoTime(); + if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) { + // timeoutMillis elapsed without anything selected. + selectCnt = 1; + } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && + selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { + // The selector returned prematurely many times in a row. + // Rebuild the selector to work around the problem. + logger.warn( + "Selector.select() returned prematurely {} times in a row; rebuilding selector.", + selectCnt); + + rebuildSelector(); + selector = this.selector; + + // Select again to populate selectedKeys. + selector.selectNow(); + selectCnt = 1; + break; + } + + currentTimeNanos = time; + } + + if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) { + if (logger.isDebugEnabled()) { + logger.debug("Selector.select() returned prematurely {} times in a row.", selectCnt - 1); + } + } + } catch (CancelledKeyException e) { + if (logger.isDebugEnabled()) { + logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector - JDK bug?", e); + } + // Harmless exception - log anyway + } + } + + private void selectAgain() { + needsToSelectAgain = false; + try { + selector.selectNow(); + } catch (Throwable t) { + logger.warn("Failed to update SelectionKeys.", t); + } + } +} diff --git a/common/src/common/net/channel/nio/NioEventLoopGroup.java b/common/src/common/net/channel/nio/NioEventLoopGroup.java new file mode 100644 index 0000000..926efc8 --- /dev/null +++ b/common/src/common/net/channel/nio/NioEventLoopGroup.java @@ -0,0 +1,87 @@ +/* + * 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 common.net.channel.nio; + +import java.nio.channels.spi.SelectorProvider; +import java.util.concurrent.ThreadFactory; + +import common.net.channel.MultithreadEventLoopGroup; +import common.net.util.concurrent.EventExecutor; + +/** + * {@link MultithreadEventLoopGroup} implementations which is used for NIO {@link Selector} based {@link Channel}s. + */ +public class NioEventLoopGroup extends MultithreadEventLoopGroup { + + /** + * Create a new instance using the default number of threads, the default {@link ThreadFactory} and + * the {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}. + */ + public NioEventLoopGroup() { + this(0); + } + + /** + * Create a new instance using the specified number of threads, {@link ThreadFactory} and the + * {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}. + */ + public NioEventLoopGroup(int nThreads) { + this(nThreads, null); + } + + /** + * Create a new instance using the specified number of threads, the given {@link ThreadFactory} and the + * {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}. + */ + public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) { + this(nThreads, threadFactory, SelectorProvider.provider()); + } + + /** + * Create a new instance using the specified number of threads, the given {@link ThreadFactory} and the given + * {@link SelectorProvider}. + */ + public NioEventLoopGroup( + int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) { + super(nThreads, threadFactory, selectorProvider); + } + + /** + * Sets the percentage of the desired amount of time spent for I/O in the child event loops. The default value is + * {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks. + */ + public void setIoRatio(int ioRatio) { + for (EventExecutor e: children()) { + ((NioEventLoop) e).setIoRatio(ioRatio); + } + } + + /** + * Replaces the current {@link Selector}s of the child event loops with newly created {@link Selector}s to work + * around the infamous epoll 100% CPU bug. + */ + public void rebuildSelectors() { + for (EventExecutor e: children()) { + ((NioEventLoop) e).rebuildSelector(); + } + } + + @Override + protected EventExecutor newChild( + ThreadFactory threadFactory, Object... args) throws Exception { + return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]); + } +} diff --git a/common/src/common/net/channel/nio/NioTask.java b/common/src/common/net/channel/nio/NioTask.java new file mode 100644 index 0000000..43dea6b --- /dev/null +++ b/common/src/common/net/channel/nio/NioTask.java @@ -0,0 +1,40 @@ +/* + * 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 common.net.channel.nio; + +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; + +/** + * An arbitrary task that can be executed by {@link NioEventLoop} when a {@link SelectableChannel} becomes ready. + * + * @see NioEventLoop#register(SelectableChannel, int, NioTask) + */ +public interface NioTask { + /** + * Invoked when the {@link SelectableChannel} has been selected by the {@link Selector}. + */ + void channelReady(C ch, SelectionKey key) throws Exception; + + /** + * Invoked when the {@link SelectionKey} of the specified {@link SelectableChannel} has been cancelled and thus + * this {@link NioTask} will not be notified anymore. + * + * @param cause the cause of the unregistration. {@code null} if a user called {@link SelectionKey#cancel()} or + * the event loop has been shut down. + */ + void channelUnregistered(C ch, Throwable cause) throws Exception; +} diff --git a/common/src/common/net/channel/nio/SelectedSelectionKeySet.java b/common/src/common/net/channel/nio/SelectedSelectionKeySet.java new file mode 100644 index 0000000..8d7cfd8 --- /dev/null +++ b/common/src/common/net/channel/nio/SelectedSelectionKeySet.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013 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 common.net.channel.nio; + +import java.nio.channels.SelectionKey; +import java.util.AbstractSet; +import java.util.Iterator; + +final class SelectedSelectionKeySet extends AbstractSet { + + private SelectionKey[] keysA; + private int keysASize; + private SelectionKey[] keysB; + private int keysBSize; + private boolean isA = true; + + SelectedSelectionKeySet() { + keysA = new SelectionKey[1024]; + keysB = keysA.clone(); + } + + @Override + public boolean add(SelectionKey o) { + if (o == null) { + return false; + } + + if (isA) { + int size = keysASize; + keysA[size ++] = o; + keysASize = size; + if (size == keysA.length) { + doubleCapacityA(); + } + } else { + int size = keysBSize; + keysB[size ++] = o; + keysBSize = size; + if (size == keysB.length) { + doubleCapacityB(); + } + } + + return true; + } + + private void doubleCapacityA() { + SelectionKey[] newKeysA = new SelectionKey[keysA.length << 1]; + System.arraycopy(keysA, 0, newKeysA, 0, keysASize); + keysA = newKeysA; + } + + private void doubleCapacityB() { + SelectionKey[] newKeysB = new SelectionKey[keysB.length << 1]; + System.arraycopy(keysB, 0, newKeysB, 0, keysBSize); + keysB = newKeysB; + } + + SelectionKey[] flip() { + if (isA) { + isA = false; + keysA[keysASize] = null; + keysBSize = 0; + return keysA; + } else { + isA = true; + keysB[keysBSize] = null; + keysASize = 0; + return keysB; + } + } + + @Override + public int size() { + if (isA) { + return keysASize; + } else { + return keysBSize; + } + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } +} diff --git a/common/src/common/net/channel/socket/ChannelInputShutdownEvent.java b/common/src/common/net/channel/socket/ChannelInputShutdownEvent.java new file mode 100644 index 0000000..3886008 --- /dev/null +++ b/common/src/common/net/channel/socket/ChannelInputShutdownEvent.java @@ -0,0 +1,33 @@ +/* + * 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 common.net.channel.socket; + +/** + * Special event which will be fired and passed to the + * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} methods once the input of + * a {@link SocketChannel} was shutdown and the {@link SocketChannelConfig#isAllowHalfClosure()} method returns + * {@code true}. + */ +public final class ChannelInputShutdownEvent { + + /** + * Instance to use + */ + + public static final ChannelInputShutdownEvent INSTANCE = new ChannelInputShutdownEvent(); + + private ChannelInputShutdownEvent() { } +} diff --git a/common/src/common/net/channel/socket/DefaultServerSocketChannelConfig.java b/common/src/common/net/channel/socket/DefaultServerSocketChannelConfig.java new file mode 100644 index 0000000..52db912 --- /dev/null +++ b/common/src/common/net/channel/socket/DefaultServerSocketChannelConfig.java @@ -0,0 +1,203 @@ +/* + * 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 common.net.channel.socket; + +import static common.net.channel.ChannelOption.SO_BACKLOG; +import static common.net.channel.ChannelOption.SO_RCVBUF; +import static common.net.channel.ChannelOption.SO_REUSEADDR; + +import java.net.ServerSocket; +import java.net.SocketException; +import java.util.Map; + +import common.net.buffer.ByteBufAllocator; +import common.net.channel.ChannelException; +import common.net.channel.ChannelOption; +import common.net.channel.DefaultChannelConfig; +import common.net.channel.MessageSizeEstimator; +import common.net.channel.RecvByteBufAllocator; +import common.net.util.NetUtil; + +/** + * The default {@link ServerSocketChannelConfig} implementation. + */ +public class DefaultServerSocketChannelConfig extends DefaultChannelConfig + implements ServerSocketChannelConfig { + + protected final ServerSocket javaSocket; + private volatile int backlog = NetUtil.SOMAXCONN; + + /** + * Creates a new instance. + */ + public DefaultServerSocketChannelConfig(ServerSocketChannel channel, ServerSocket javaSocket) { + super(channel); + if (javaSocket == null) { + throw new NullPointerException("javaSocket"); + } + this.javaSocket = javaSocket; + } + + @Override + public Map, Object> getOptions() { + return getOptions(super.getOptions(), SO_RCVBUF, SO_REUSEADDR, SO_BACKLOG); + } + + + @Override + public T getOption(ChannelOption option) { + if (option == SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == SO_REUSEADDR) { + return (T) Boolean.valueOf(isReuseAddress()); + } + if (option == SO_BACKLOG) { + return (T) Integer.valueOf(getBacklog()); + } + + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == SO_REUSEADDR) { + setReuseAddress((Boolean) value); + } else if (option == SO_BACKLOG) { + setBacklog((Integer) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + @Override + public boolean isReuseAddress() { + try { + return javaSocket.getReuseAddress(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public ServerSocketChannelConfig setReuseAddress(boolean reuseAddress) { + try { + javaSocket.setReuseAddress(reuseAddress); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public int getReceiveBufferSize() { + try { + return javaSocket.getReceiveBufferSize(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public ServerSocketChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + javaSocket.setReceiveBufferSize(receiveBufferSize); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public ServerSocketChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + javaSocket.setPerformancePreferences(connectionTime, latency, bandwidth); + return this; + } + + @Override + public int getBacklog() { + return backlog; + } + + @Override + public ServerSocketChannelConfig setBacklog(int backlog) { + if (backlog < 0) { + throw new IllegalArgumentException("backlog: " + backlog); + } + this.backlog = backlog; + return this; + } + + @Override + public ServerSocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + public ServerSocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public ServerSocketChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public ServerSocketChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public ServerSocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public ServerSocketChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public ServerSocketChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + public ServerSocketChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public ServerSocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/common/src/common/net/channel/socket/DefaultSocketChannelConfig.java b/common/src/common/net/channel/socket/DefaultSocketChannelConfig.java new file mode 100644 index 0000000..dce2ffb --- /dev/null +++ b/common/src/common/net/channel/socket/DefaultSocketChannelConfig.java @@ -0,0 +1,348 @@ +/* + * 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 common.net.channel.socket; + +import static common.net.channel.ChannelOption.ALLOW_HALF_CLOSURE; +import static common.net.channel.ChannelOption.IP_TOS; +import static common.net.channel.ChannelOption.SO_KEEPALIVE; +import static common.net.channel.ChannelOption.SO_LINGER; +import static common.net.channel.ChannelOption.SO_RCVBUF; +import static common.net.channel.ChannelOption.SO_REUSEADDR; +import static common.net.channel.ChannelOption.SO_SNDBUF; +import static common.net.channel.ChannelOption.TCP_NODELAY; + +import java.net.Socket; +import java.net.SocketException; +import java.util.Map; + +import common.net.buffer.ByteBufAllocator; +import common.net.channel.ChannelException; +import common.net.channel.ChannelOption; +import common.net.channel.DefaultChannelConfig; +import common.net.channel.MessageSizeEstimator; +import common.net.channel.RecvByteBufAllocator; +import common.net.util.internal.PlatformDependent; + +/** + * The default {@link SocketChannelConfig} implementation. + */ +public class DefaultSocketChannelConfig extends DefaultChannelConfig + implements SocketChannelConfig { + + protected final Socket javaSocket; + private volatile boolean allowHalfClosure; + + /** + * Creates a new instance. + */ + public DefaultSocketChannelConfig(SocketChannel channel, Socket javaSocket) { + super(channel); + if (javaSocket == null) { + throw new NullPointerException("javaSocket"); + } + this.javaSocket = javaSocket; + + // Enable TCP_NODELAY by default if possible. + if (PlatformDependent.canEnableTcpNoDelayByDefault()) { + try { + setTcpNoDelay(true); + } catch (Exception e) { + // Ignore. + } + } + } + + @Override + public Map, Object> getOptions() { + return getOptions( + super.getOptions(), + SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS, + ALLOW_HALF_CLOSURE); + } + + + @Override + public T getOption(ChannelOption option) { + if (option == SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == SO_SNDBUF) { + return (T) Integer.valueOf(getSendBufferSize()); + } + if (option == TCP_NODELAY) { + return (T) Boolean.valueOf(isTcpNoDelay()); + } + if (option == SO_KEEPALIVE) { + return (T) Boolean.valueOf(isKeepAlive()); + } + if (option == SO_REUSEADDR) { + return (T) Boolean.valueOf(isReuseAddress()); + } + if (option == SO_LINGER) { + return (T) Integer.valueOf(getSoLinger()); + } + if (option == IP_TOS) { + return (T) Integer.valueOf(getTrafficClass()); + } + if (option == ALLOW_HALF_CLOSURE) { + return (T) Boolean.valueOf(isAllowHalfClosure()); + } + + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == SO_SNDBUF) { + setSendBufferSize((Integer) value); + } else if (option == TCP_NODELAY) { + setTcpNoDelay((Boolean) value); + } else if (option == SO_KEEPALIVE) { + setKeepAlive((Boolean) value); + } else if (option == SO_REUSEADDR) { + setReuseAddress((Boolean) value); + } else if (option == SO_LINGER) { + setSoLinger((Integer) value); + } else if (option == IP_TOS) { + setTrafficClass((Integer) value); + } else if (option == ALLOW_HALF_CLOSURE) { + setAllowHalfClosure((Boolean) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + @Override + public int getReceiveBufferSize() { + try { + return javaSocket.getReceiveBufferSize(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public int getSendBufferSize() { + try { + return javaSocket.getSendBufferSize(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public int getSoLinger() { + try { + return javaSocket.getSoLinger(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public int getTrafficClass() { + try { + return javaSocket.getTrafficClass(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isKeepAlive() { + try { + return javaSocket.getKeepAlive(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isReuseAddress() { + try { + return javaSocket.getReuseAddress(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isTcpNoDelay() { + try { + return javaSocket.getTcpNoDelay(); + } catch (SocketException e) { + throw new ChannelException(e); + } + } + + @Override + public SocketChannelConfig setKeepAlive(boolean keepAlive) { + try { + javaSocket.setKeepAlive(keepAlive); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SocketChannelConfig setPerformancePreferences( + int connectionTime, int latency, int bandwidth) { + javaSocket.setPerformancePreferences(connectionTime, latency, bandwidth); + return this; + } + + @Override + public SocketChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + javaSocket.setReceiveBufferSize(receiveBufferSize); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SocketChannelConfig setReuseAddress(boolean reuseAddress) { + try { + javaSocket.setReuseAddress(reuseAddress); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SocketChannelConfig setSendBufferSize(int sendBufferSize) { + try { + javaSocket.setSendBufferSize(sendBufferSize); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SocketChannelConfig setSoLinger(int soLinger) { + try { + if (soLinger < 0) { + javaSocket.setSoLinger(false, 0); + } else { + javaSocket.setSoLinger(true, soLinger); + } + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SocketChannelConfig setTcpNoDelay(boolean tcpNoDelay) { + try { + javaSocket.setTcpNoDelay(tcpNoDelay); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SocketChannelConfig setTrafficClass(int trafficClass) { + try { + javaSocket.setTrafficClass(trafficClass); + } catch (SocketException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public boolean isAllowHalfClosure() { + return allowHalfClosure; + } + + @Override + public SocketChannelConfig setAllowHalfClosure(boolean allowHalfClosure) { + this.allowHalfClosure = allowHalfClosure; + return this; + } + + @Override + public SocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + public SocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public SocketChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public SocketChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public SocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public SocketChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public SocketChannelConfig setAutoClose(boolean autoClose) { + super.setAutoClose(autoClose); + return this; + } + + @Override + public SocketChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + public SocketChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public SocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/common/src/common/net/channel/socket/ServerSocketChannel.java b/common/src/common/net/channel/socket/ServerSocketChannel.java new file mode 100644 index 0000000..8808778 --- /dev/null +++ b/common/src/common/net/channel/socket/ServerSocketChannel.java @@ -0,0 +1,32 @@ +/* + * 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 common.net.channel.socket; + +import java.net.InetSocketAddress; + +import common.net.channel.ServerChannel; + +/** + * A TCP/IP {@link ServerChannel} which accepts incoming TCP/IP connections. + */ +public interface ServerSocketChannel extends ServerChannel { + @Override + ServerSocketChannelConfig config(); + @Override + InetSocketAddress localAddress(); + @Override + InetSocketAddress remoteAddress(); +} diff --git a/common/src/common/net/channel/socket/ServerSocketChannelConfig.java b/common/src/common/net/channel/socket/ServerSocketChannelConfig.java new file mode 100644 index 0000000..56138d6 --- /dev/null +++ b/common/src/common/net/channel/socket/ServerSocketChannelConfig.java @@ -0,0 +1,104 @@ +/* + * 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 common.net.channel.socket; + +import common.net.buffer.ByteBufAllocator; +import common.net.channel.ChannelConfig; +import common.net.channel.MessageSizeEstimator; +import common.net.channel.RecvByteBufAllocator; + +/** + * A {@link ChannelConfig} for a {@link ServerSocketChannel}. + * + *

Available options

+ * + * In addition to the options provided by {@link ChannelConfig}, + * {@link ServerSocketChannelConfig} allows the following options in the + * option map: + * + * + * + * + * + * + * + * + * + * + * + *
NameAssociated setter method
{@code "backlog"}{@link #setBacklog(int)}
{@code "reuseAddress"}{@link #setReuseAddress(boolean)}
{@code "receiveBufferSize"}{@link #setReceiveBufferSize(int)}
+ */ +public interface ServerSocketChannelConfig extends ChannelConfig { + + /** + * Gets the backlog value to specify when the channel binds to a local + * address. + */ + int getBacklog(); + + /** + * Sets the backlog value to specify when the channel binds to a local + * address. + */ + ServerSocketChannelConfig setBacklog(int backlog); + + /** + * Gets the {@link StandardSocketOptions#SO_REUSEADDR} option. + */ + boolean isReuseAddress(); + + /** + * Sets the {@link StandardSocketOptions#SO_REUSEADDR} option. + */ + ServerSocketChannelConfig setReuseAddress(boolean reuseAddress); + + /** + * Gets the {@link StandardSocketOptions#SO_RCVBUF} option. + */ + int getReceiveBufferSize(); + + /** + * Gets the {@link StandardSocketOptions#SO_SNDBUF} option. + */ + ServerSocketChannelConfig setReceiveBufferSize(int receiveBufferSize); + + /** + * Sets the performance preferences as specified in + * {@link ServerSocket#setPerformancePreferences(int, int, int)}. + */ + ServerSocketChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth); + + @Override + ServerSocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); + + @Override + ServerSocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); + + @Override + ServerSocketChannelConfig setWriteSpinCount(int writeSpinCount); + + @Override + ServerSocketChannelConfig setAllocator(ByteBufAllocator allocator); + + @Override + ServerSocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); + + @Override + ServerSocketChannelConfig setAutoRead(boolean autoRead); + + @Override + ServerSocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator); +} diff --git a/common/src/common/net/channel/socket/SocketChannel.java b/common/src/common/net/channel/socket/SocketChannel.java new file mode 100644 index 0000000..f2b3006 --- /dev/null +++ b/common/src/common/net/channel/socket/SocketChannel.java @@ -0,0 +1,61 @@ +/* + * 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 common.net.channel.socket; + +import java.net.InetSocketAddress; + +import common.net.channel.Channel; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelPromise; + +/** + * A TCP/IP socket {@link Channel}. + */ +public interface SocketChannel extends Channel { + @Override + ServerSocketChannel parent(); + + @Override + SocketChannelConfig config(); + @Override + InetSocketAddress localAddress(); + @Override + InetSocketAddress remoteAddress(); + + /** + * Returns {@code true} if and only if the remote peer shut down its output so that no more + * data is received from this channel. Note that the semantic of this method is different from + * that of {@link Socket#shutdownInput()} and {@link Socket#isInputShutdown()}. + */ + boolean isInputShutdown(); + + /** + * @see Socket#isOutputShutdown() + */ + boolean isOutputShutdown(); + + /** + * @see Socket#shutdownOutput() + */ + ChannelFuture shutdownOutput(); + + /** + * @see Socket#shutdownOutput() + * + * Will notify the given {@link ChannelPromise} + */ + ChannelFuture shutdownOutput(ChannelPromise future); +} diff --git a/common/src/common/net/channel/socket/SocketChannelConfig.java b/common/src/common/net/channel/socket/SocketChannelConfig.java new file mode 100644 index 0000000..044f91e --- /dev/null +++ b/common/src/common/net/channel/socket/SocketChannelConfig.java @@ -0,0 +1,177 @@ +/* + * 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 common.net.channel.socket; + +import common.net.buffer.ByteBufAllocator; +import common.net.channel.ChannelConfig; +import common.net.channel.MessageSizeEstimator; +import common.net.channel.RecvByteBufAllocator; + +/** + * A {@link ChannelConfig} for a {@link SocketChannel}. + * + *

Available options

+ * + * In addition to the options provided by {@link ChannelConfig}, + * {@link SocketChannelConfig} allows the following options in the option map: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameAssociated setter method
{@link ChannelOption#SO_KEEPALIVE}{@link #setKeepAlive(boolean)}
{@link ChannelOption#SO_REUSEADDR}{@link #setReuseAddress(boolean)}
{@link ChannelOption#SO_LINGER}{@link #setSoLinger(int)}
{@link ChannelOption#TCP_NODELAY}{@link #setTcpNoDelay(boolean)}
{@link ChannelOption#SO_RCVBUF}{@link #setReceiveBufferSize(int)}
{@link ChannelOption#SO_SNDBUF}{@link #setSendBufferSize(int)}
{@link ChannelOption#IP_TOS}{@link #setTrafficClass(int)}
{@link ChannelOption#ALLOW_HALF_CLOSURE}{@link #setAllowHalfClosure(boolean)}
+ */ +public interface SocketChannelConfig extends ChannelConfig { + + /** + * Gets the {@link StandardSocketOptions#TCP_NODELAY} option. Please note that the default value of this option + * is {@code true} unlike the operating system default ({@code false}). However, for some buggy platforms, such as + * Android, that shows erratic behavior with Nagle's algorithm disabled, the default value remains to be + * {@code false}. + */ + boolean isTcpNoDelay(); + + /** + * Sets the {@link StandardSocketOptions#TCP_NODELAY} option. Please note that the default value of this option + * is {@code true} unlike the operating system default ({@code false}). However, for some buggy platforms, such as + * Android, that shows erratic behavior with Nagle's algorithm disabled, the default value remains to be + * {@code false}. + */ + SocketChannelConfig setTcpNoDelay(boolean tcpNoDelay); + + /** + * Gets the {@link StandardSocketOptions#SO_LINGER} option. + */ + int getSoLinger(); + + /** + * Sets the {@link StandardSocketOptions#SO_LINGER} option. + */ + SocketChannelConfig setSoLinger(int soLinger); + + /** + * Gets the {@link StandardSocketOptions#SO_SNDBUF} option. + */ + int getSendBufferSize(); + + /** + * Sets the {@link StandardSocketOptions#SO_SNDBUF} option. + */ + SocketChannelConfig setSendBufferSize(int sendBufferSize); + + /** + * Gets the {@link StandardSocketOptions#SO_RCVBUF} option. + */ + int getReceiveBufferSize(); + + /** + * Sets the {@link StandardSocketOptions#SO_RCVBUF} option. + */ + SocketChannelConfig setReceiveBufferSize(int receiveBufferSize); + + /** + * Gets the {@link StandardSocketOptions#SO_KEEPALIVE} option. + */ + boolean isKeepAlive(); + + /** + * Sets the {@link StandardSocketOptions#SO_KEEPALIVE} option. + */ + SocketChannelConfig setKeepAlive(boolean keepAlive); + + /** + * Gets the {@link StandardSocketOptions#IP_TOS} option. + */ + int getTrafficClass(); + + /** + * Sets the {@link StandardSocketOptions#IP_TOS} option. + */ + SocketChannelConfig setTrafficClass(int trafficClass); + + /** + * Gets the {@link StandardSocketOptions#SO_REUSEADDR} option. + */ + boolean isReuseAddress(); + + /** + * Sets the {@link StandardSocketOptions#SO_REUSEADDR} option. + */ + SocketChannelConfig setReuseAddress(boolean reuseAddress); + + /** + * Sets the performance preferences as specified in + * {@link Socket#setPerformancePreferences(int, int, int)}. + */ + SocketChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth); + + /** + * Returns {@code true} if and only if the channel should not close itself when its remote + * peer shuts down output to make the connection half-closed. If {@code false}, the connection + * is closed automatically when the remote peer shuts down output. + */ + boolean isAllowHalfClosure(); + + /** + * Sets whether the channel should not close itself when its remote peer shuts down output to + * make the connection half-closed. If {@code true} the connection is not closed when the + * remote peer shuts down output. Instead, + * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)} + * is invoked with a {@link ChannelInputShutdownEvent} object. If {@code false}, the connection + * is closed automatically. + */ + SocketChannelConfig setAllowHalfClosure(boolean allowHalfClosure); + + @Override + SocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); + + @Override + SocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); + + @Override + SocketChannelConfig setWriteSpinCount(int writeSpinCount); + + @Override + SocketChannelConfig setAllocator(ByteBufAllocator allocator); + + @Override + SocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); + + @Override + SocketChannelConfig setAutoRead(boolean autoRead); + + @Override + SocketChannelConfig setAutoClose(boolean autoClose); + + @Override + SocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator); +} diff --git a/common/src/common/net/channel/socket/nio/NioServerSocketChannel.java b/common/src/common/net/channel/socket/nio/NioServerSocketChannel.java new file mode 100644 index 0000000..b006e1f --- /dev/null +++ b/common/src/common/net/channel/socket/nio/NioServerSocketChannel.java @@ -0,0 +1,197 @@ +/* + * 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 common.net.channel.socket.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.List; + +import common.net.channel.ChannelException; +import common.net.channel.ChannelMetadata; +import common.net.channel.ChannelOutboundBuffer; +import common.net.channel.nio.AbstractNioMessageChannel; +import common.net.channel.socket.DefaultServerSocketChannelConfig; +import common.net.channel.socket.ServerSocketChannelConfig; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A {@link game.net.channel.socket.ServerSocketChannel} implementation which uses + * NIO selector based implementation to accept new connections. + */ +public class NioServerSocketChannel extends AbstractNioMessageChannel + implements common.net.channel.socket.ServerSocketChannel { + + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketChannel.class); + + private static ServerSocketChannel newSocket(SelectorProvider provider) { + try { + /** + * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in + * {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise. + * + * See #2308. + */ + return provider.openServerSocketChannel(); + } catch (IOException e) { + throw new ChannelException( + "Failed to open a server socket.", e); + } + } + + private final ServerSocketChannelConfig config; + + /** + * Create a new instance + */ + public NioServerSocketChannel() { + this(newSocket(DEFAULT_SELECTOR_PROVIDER)); + } + + /** + * Create a new instance using the given {@link SelectorProvider}. + */ + public NioServerSocketChannel(SelectorProvider provider) { + this(newSocket(provider)); + } + + /** + * Create a new instance using the given {@link ServerSocketChannel}. + */ + public NioServerSocketChannel(ServerSocketChannel channel) { + super(null, channel, SelectionKey.OP_ACCEPT); + config = new NioServerSocketChannelConfig(this, javaChannel().socket()); + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public ServerSocketChannelConfig config() { + return config; + } + + @Override + public boolean isActive() { + return javaChannel().socket().isBound(); + } + + @Override + public InetSocketAddress remoteAddress() { + return null; + } + + @Override + protected ServerSocketChannel javaChannel() { + return (ServerSocketChannel) super.javaChannel(); + } + + @Override + protected SocketAddress localAddress0() { + return javaChannel().socket().getLocalSocketAddress(); + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + javaChannel().socket().bind(localAddress, config.getBacklog()); + } + + @Override + protected void doClose() throws Exception { + javaChannel().close(); + } + + @Override + protected int doReadMessages(List buf) throws Exception { + SocketChannel ch = javaChannel().accept(); + + try { + if (ch != null) { + buf.add(new NioSocketChannel(this, ch)); + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } + + // Unnecessary stuff + @Override + protected boolean doConnect( + SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected void doFinishConnect() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected SocketAddress remoteAddress0() { + return null; + } + + @Override + protected void doDisconnect() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected final Object filterOutboundMessage(Object msg) throws Exception { + throw new UnsupportedOperationException(); + } + + private final class NioServerSocketChannelConfig extends DefaultServerSocketChannelConfig { + private NioServerSocketChannelConfig(NioServerSocketChannel channel, ServerSocket javaSocket) { + super(channel, javaSocket); + } + + @Override + protected void autoReadCleared() { + setReadPending(false); + } + } +} diff --git a/common/src/common/net/channel/socket/nio/NioSocketChannel.java b/common/src/common/net/channel/socket/nio/NioSocketChannel.java new file mode 100644 index 0000000..1ce8c3b --- /dev/null +++ b/common/src/common/net/channel/socket/nio/NioSocketChannel.java @@ -0,0 +1,320 @@ +/* + * 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 common.net.channel.socket.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; + +import common.net.buffer.ByteBuf; +import common.net.channel.Channel; +import common.net.channel.ChannelException; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelMetadata; +import common.net.channel.ChannelOutboundBuffer; +import common.net.channel.ChannelPromise; +import common.net.channel.EventLoop; +import common.net.channel.nio.AbstractNioByteChannel; +import common.net.channel.socket.DefaultSocketChannelConfig; +import common.net.channel.socket.ServerSocketChannel; +import common.net.channel.socket.SocketChannelConfig; +import common.net.util.internal.OneTimeTask; + +/** + * {@link game.net.channel.socket.SocketChannel} which uses NIO selector based implementation. + */ +public class NioSocketChannel extends AbstractNioByteChannel implements common.net.channel.socket.SocketChannel { + + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); + + private static SocketChannel newSocket(SelectorProvider provider) { + try { + /** + * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in + * {@link SelectorProvider#provider()} which is called by each SocketChannel.open() otherwise. + * + * See #2308. + */ + return provider.openSocketChannel(); + } catch (IOException e) { + throw new ChannelException("Failed to open a socket.", e); + } + } + + private final SocketChannelConfig config; + + /** + * Create a new instance + */ + public NioSocketChannel() { + this(newSocket(DEFAULT_SELECTOR_PROVIDER)); + } + + /** + * Create a new instance using the given {@link SelectorProvider}. + */ + public NioSocketChannel(SelectorProvider provider) { + this(newSocket(provider)); + } + + /** + * Create a new instance using the given {@link SocketChannel}. + */ + public NioSocketChannel(SocketChannel socket) { + this(null, socket); + } + + /** + * Create a new instance + * + * @param parent the {@link Channel} which created this instance or {@code null} if it was created by the user + * @param socket the {@link SocketChannel} which will be used + */ + public NioSocketChannel(Channel parent, SocketChannel socket) { + super(parent, socket); + config = new NioSocketChannelConfig(this, socket.socket()); + } + + @Override + public ServerSocketChannel parent() { + return (ServerSocketChannel) super.parent(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public SocketChannelConfig config() { + return config; + } + + @Override + protected SocketChannel javaChannel() { + return (SocketChannel) super.javaChannel(); + } + + @Override + public boolean isActive() { + SocketChannel ch = javaChannel(); + return ch.isOpen() && ch.isConnected(); + } + + @Override + public boolean isInputShutdown() { + return super.isInputShutdown(); + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public InetSocketAddress remoteAddress() { + return (InetSocketAddress) super.remoteAddress(); + } + + @Override + public boolean isOutputShutdown() { + return javaChannel().socket().isOutputShutdown() || !isActive(); + } + + @Override + public ChannelFuture shutdownOutput() { + return shutdownOutput(newPromise()); + } + + @Override + public ChannelFuture shutdownOutput(final ChannelPromise promise) { + EventLoop loop = eventLoop(); + if (loop.inEventLoop()) { + try { + javaChannel().socket().shutdownOutput(); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + loop.execute(new OneTimeTask() { + @Override + public void run() { + shutdownOutput(promise); + } + }); + } + return promise; + } + + @Override + protected SocketAddress localAddress0() { + return javaChannel().socket().getLocalSocketAddress(); + } + + @Override + protected SocketAddress remoteAddress0() { + return javaChannel().socket().getRemoteSocketAddress(); + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + javaChannel().socket().bind(localAddress); + } + + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (localAddress != null) { + javaChannel().socket().bind(localAddress); + } + + boolean success = false; + try { + boolean connected = javaChannel().connect(remoteAddress); + if (!connected) { + selectionKey().interestOps(SelectionKey.OP_CONNECT); + } + success = true; + return connected; + } finally { + if (!success) { + doClose(); + } + } + } + + @Override + protected void doFinishConnect() throws Exception { + if (!javaChannel().finishConnect()) { + throw new Error(); + } + } + + @Override + protected void doDisconnect() throws Exception { + doClose(); + } + + @Override + protected void doClose() throws Exception { + javaChannel().close(); + } + + @Override + protected int doReadBytes(ByteBuf byteBuf) throws Exception { + return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes()); + } + + @Override + protected int doWriteBytes(ByteBuf buf) throws Exception { + final int expectedWrittenBytes = buf.readableBytes(); + return buf.readBytes(javaChannel(), expectedWrittenBytes); + } + +// @Override +// protected long doWriteFileRegion(FileRegion region) throws Exception { +// final long position = region.transfered(); +// return region.transferTo(javaChannel(), position); +// } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + for (;;) { + int size = in.size(); + if (size == 0) { + // All written so clear OP_WRITE + clearOpWrite(); + break; + } + long writtenBytes = 0; + boolean done = false; + boolean setOpWrite = false; + + // Ensure the pending writes are made of ByteBufs only. + ByteBuffer[] nioBuffers = in.nioBuffers(); + int nioBufferCnt = in.nioBufferCount(); + long expectedWrittenBytes = in.nioBufferSize(); + SocketChannel ch = javaChannel(); + + // Always us nioBuffers() to workaround data-corruption. + // See https://github.com/netty/netty/issues/2761 + switch (nioBufferCnt) { + case 0: + // We have something else beside ByteBuffers to write so fallback to normal writes. + super.doWrite(in); + return; + case 1: + // Only one ByteBuf so use non-gathering write + ByteBuffer nioBuffer = nioBuffers[0]; + for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) { + final int localWrittenBytes = ch.write(nioBuffer); + if (localWrittenBytes == 0) { + setOpWrite = true; + break; + } + expectedWrittenBytes -= localWrittenBytes; + writtenBytes += localWrittenBytes; + if (expectedWrittenBytes == 0) { + done = true; + break; + } + } + break; + default: + for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) { + final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt); + if (localWrittenBytes == 0) { + setOpWrite = true; + break; + } + expectedWrittenBytes -= localWrittenBytes; + writtenBytes += localWrittenBytes; + if (expectedWrittenBytes == 0) { + done = true; + break; + } + } + break; + } + + // Release the fully written buffers, and update the indexes of the partially written buffer. + in.removeBytes(writtenBytes); + + if (!done) { + // Did not write all buffers completely. + incompleteWrite(setOpWrite); + break; + } + } + } + + private final class NioSocketChannelConfig extends DefaultSocketChannelConfig { + private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) { + super(channel, javaSocket); + } + + @Override + protected void autoReadCleared() { + setReadPending(false); + } + } +} diff --git a/common/src/common/net/handler/codec/ByteToMessageDecoder.java b/common/src/common/net/handler/codec/ByteToMessageDecoder.java new file mode 100644 index 0000000..9957e17 --- /dev/null +++ b/common/src/common/net/handler/codec/ByteToMessageDecoder.java @@ -0,0 +1,306 @@ +/* + * 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 common.net.handler.codec; + +import java.util.List; + +import common.net.buffer.ByteBuf; +import common.net.buffer.Unpooled; +import common.net.channel.ChannelHandlerContext; +import common.net.channel.ChannelInboundHandlerAdapter; +import common.net.util.internal.RecyclableArrayList; +import common.net.util.internal.StringUtil; + +/** + * {@link ChannelInboundHandlerAdapter} which decodes bytes in a stream-like fashion from one {@link ByteBuf} to an + * other Message type. + * + * For example here is an implementation which reads all readable bytes from + * the input {@link ByteBuf} and create a new {@link ByteBuf}. + * + *
+ *     public class SquareDecoder extends {@link ByteToMessageDecoder} {
+ *         {@code @Override}
+ *         public void decode({@link ChannelHandlerContext} ctx, {@link ByteBuf} in, List<Object> out)
+ *                 throws {@link Exception} {
+ *             out.add(in.readBytes(in.readableBytes()));
+ *         }
+ *     }
+ * 
+ * + * Be aware that sub-classes of {@link ByteToMessageDecoder} MUST NOT + * annotated with {@link @Sharable}. + */ +public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter { + + ByteBuf cumulation; + private boolean singleDecode; + private boolean decodeWasNull; + private boolean first; + + protected ByteToMessageDecoder() { + if (isSharable()) { + throw new IllegalStateException("@Sharable annotation is not allowed"); + } + } + + /** + * If set then only one message is decoded on each {@link #channelRead(ChannelHandlerContext, Object)} + * call. This may be useful if you need to do some protocol upgrade and want to make sure nothing is mixed up. + * + * Default is {@code false} as this has performance impacts. + */ + public void setSingleDecode(boolean singleDecode) { + this.singleDecode = singleDecode; + } + + /** + * If {@code true} then only one message is decoded on each + * {@link #channelRead(ChannelHandlerContext, Object)} call. + * + * Default is {@code false} as this has performance impacts. + */ + public boolean isSingleDecode() { + return singleDecode; + } + + /** + * Returns the actual number of readable bytes in the internal cumulative + * buffer of this decoder. You usually do not need to rely on this value + * to write a decoder. Use it only when you must use it at your own risk. + * This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}. + */ + protected int actualReadableBytes() { + return internalBuffer().readableBytes(); + } + + /** + * Returns the internal cumulative buffer of this decoder. You usually + * do not need to access the internal buffer directly to write a decoder. + * Use it only when you must use it at your own risk. + */ + protected ByteBuf internalBuffer() { + if (cumulation != null) { + return cumulation; + } else { + return Unpooled.EMPTY_BUFFER; + } + } + + @Override + public final void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + ByteBuf buf = internalBuffer(); + int readable = buf.readableBytes(); + if (buf.isReadable()) { + ByteBuf bytes = buf.readBytes(readable); + buf.release(); + ctx.fireChannelRead(bytes); + } else { + buf.release(); + } + cumulation = null; + ctx.fireChannelReadComplete(); + handlerRemoved0(ctx); + } + + /** + * Gets called after the {@link ByteToMessageDecoder} was removed from the actual context and it doesn't handle + * events anymore. + */ + protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof ByteBuf) { + RecyclableArrayList out = RecyclableArrayList.newInstance(); + try { + ByteBuf data = (ByteBuf) msg; + first = cumulation == null; + if (first) { + cumulation = data; + } else { + if (cumulation.writerIndex() > cumulation.maxCapacity() - data.readableBytes() + || cumulation.refCnt() > 1) { + // Expand cumulation (by replace it) when either there is not more room in the buffer + // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or + // duplicate().retain(). + // + // See: + // - https://github.com/netty/netty/issues/2327 + // - https://github.com/netty/netty/issues/1764 + expandCumulation(ctx, data.readableBytes()); + } + cumulation.writeBytes(data); + data.release(); + } + callDecode(ctx, cumulation, out); + } catch (DecoderException e) { + throw e; + } catch (Throwable t) { + throw new DecoderException(t); + } finally { + if (cumulation != null && !cumulation.isReadable()) { + cumulation.release(); + cumulation = null; + } + int size = out.size(); + decodeWasNull = size == 0; + + for (int i = 0; i < size; i ++) { + ctx.fireChannelRead(out.get(i)); + } + out.recycle(); + } + } else { + ctx.fireChannelRead(msg); + } + } + + private void expandCumulation(ChannelHandlerContext ctx, int readable) { + ByteBuf oldCumulation = cumulation; + cumulation = ctx.alloc().buffer(oldCumulation.readableBytes() + readable); + cumulation.writeBytes(oldCumulation); + oldCumulation.release(); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (cumulation != null && !first && cumulation.refCnt() == 1) { + // discard some bytes if possible to make more room in the + // buffer but only if the refCnt == 1 as otherwise the user may have + // used slice().retain() or duplicate().retain(). + // + // See: + // - https://github.com/netty/netty/issues/2327 + // - https://github.com/netty/netty/issues/1764 + cumulation.discardSomeReadBytes(); + } + if (decodeWasNull) { + decodeWasNull = false; + if (!ctx.channel().config().isAutoRead()) { + ctx.read(); + } + } + ctx.fireChannelReadComplete(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + RecyclableArrayList out = RecyclableArrayList.newInstance(); + try { + if (cumulation != null) { + callDecode(ctx, cumulation, out); + decodeLast(ctx, cumulation, out); + } else { + decodeLast(ctx, Unpooled.EMPTY_BUFFER, out); + } + } catch (DecoderException e) { + throw e; + } catch (Exception e) { + throw new DecoderException(e); + } finally { + try { + if (cumulation != null) { + cumulation.release(); + cumulation = null; + } + int size = out.size(); + for (int i = 0; i < size; i++) { + ctx.fireChannelRead(out.get(i)); + } + if (size > 0) { + // Something was read, call fireChannelReadComplete() + ctx.fireChannelReadComplete(); + } + ctx.fireChannelInactive(); + } finally { + // recycle in all cases + out.recycle(); + } + } + } + + /** + * Called once data should be decoded from the given {@link ByteBuf}. This method will call + * {@link #decode(ChannelHandlerContext, ByteBuf, List)} as long as decoding should take place. + * + * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to + * @param in the {@link ByteBuf} from which to read data + * @param out the {@link List} to which decoded messages should be added + */ + protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List out) { + try { + while (in.isReadable()) { + int outSize = out.size(); + int oldInputLength = in.readableBytes(); + decode(ctx, in, out); + + // Check if this handler was removed before continuing the loop. + // If it was removed, it is not safe to continue to operate on the buffer. + // + // See https://github.com/netty/netty/issues/1664 + if (ctx.isRemoved()) { + break; + } + + if (outSize == out.size()) { + if (oldInputLength == in.readableBytes()) { + break; + } else { + continue; + } + } + + if (oldInputLength == in.readableBytes()) { + throw new DecoderException( + StringUtil.simpleClassName(getClass()) + + ".decode() did not read anything but decoded a message."); + } + + if (isSingleDecode()) { + break; + } + } + } catch (DecoderException e) { + throw e; + } catch (Throwable cause) { + throw new DecoderException(cause); + } + } + + /** + * Decode the from one {@link ByteBuf} to an other. This method will be called till either the input + * {@link ByteBuf} has nothing to read when return from this method or till nothing was read from the input + * {@link ByteBuf}. + * + * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to + * @param in the {@link ByteBuf} from which to read data + * @param out the {@link List} to which decoded messages should be added + * @throws Exception is thrown if an error accour + */ + protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception; + + /** + * Is called one last time when the {@link ChannelHandlerContext} goes in-active. Which means the + * {@link #channelInactive(ChannelHandlerContext)} was triggered. + * + * By default this will just call {@link #decode(ChannelHandlerContext, ByteBuf, List)} but sub-classes may + * override this for some special cleanup operation. + */ + protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + decode(ctx, in, out); + } +} diff --git a/common/src/common/net/handler/codec/CodecException.java b/common/src/common/net/handler/codec/CodecException.java new file mode 100644 index 0000000..e024083 --- /dev/null +++ b/common/src/common/net/handler/codec/CodecException.java @@ -0,0 +1,51 @@ +/* + * 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 common.net.handler.codec; + +/** + * An {@link Exception} which is thrown by a codec. + */ +public class CodecException extends RuntimeException { + + private static final long serialVersionUID = -1464830400709348473L; + + /** + * Creates a new instance. + */ + public CodecException() { + } + + /** + * Creates a new instance. + */ + public CodecException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance. + */ + public CodecException(String message) { + super(message); + } + + /** + * Creates a new instance. + */ + public CodecException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/handler/codec/CorruptedFrameException.java b/common/src/common/net/handler/codec/CorruptedFrameException.java new file mode 100644 index 0000000..d62f750 --- /dev/null +++ b/common/src/common/net/handler/codec/CorruptedFrameException.java @@ -0,0 +1,52 @@ +/* + * 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 common.net.handler.codec; + +/** + * An {@link DecoderException} which is thrown when the received frame data could not be decoded by + * an inbound handler. + */ +public class CorruptedFrameException extends DecoderException { + + private static final long serialVersionUID = 3918052232492988408L; + + /** + * Creates a new instance. + */ + public CorruptedFrameException() { + } + + /** + * Creates a new instance. + */ + public CorruptedFrameException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance. + */ + public CorruptedFrameException(String message) { + super(message); + } + + /** + * Creates a new instance. + */ + public CorruptedFrameException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/handler/codec/DecoderException.java b/common/src/common/net/handler/codec/DecoderException.java new file mode 100644 index 0000000..35f2beb --- /dev/null +++ b/common/src/common/net/handler/codec/DecoderException.java @@ -0,0 +1,51 @@ +/* + * 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 common.net.handler.codec; + +/** + * An {@link CodecException} which is thrown by a dencoder. + */ +public class DecoderException extends CodecException { + + private static final long serialVersionUID = 6926716840699621852L; + + /** + * Creates a new instance. + */ + public DecoderException() { + } + + /** + * Creates a new instance. + */ + public DecoderException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance. + */ + public DecoderException(String message) { + super(message); + } + + /** + * Creates a new instance. + */ + public DecoderException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/handler/codec/EncoderException.java b/common/src/common/net/handler/codec/EncoderException.java new file mode 100644 index 0000000..549455d --- /dev/null +++ b/common/src/common/net/handler/codec/EncoderException.java @@ -0,0 +1,51 @@ +/* + * 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 common.net.handler.codec; + +/** + * An {@link CodecException} which is thrown by an encoder. + */ +public class EncoderException extends CodecException { + + private static final long serialVersionUID = -5086121160476476774L; + + /** + * Creates a new instance. + */ + public EncoderException() { + } + + /** + * Creates a new instance. + */ + public EncoderException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new instance. + */ + public EncoderException(String message) { + super(message); + } + + /** + * Creates a new instance. + */ + public EncoderException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/handler/codec/MessageToByteEncoder.java b/common/src/common/net/handler/codec/MessageToByteEncoder.java new file mode 100644 index 0000000..9cb059d --- /dev/null +++ b/common/src/common/net/handler/codec/MessageToByteEncoder.java @@ -0,0 +1,154 @@ +/* + * 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 common.net.handler.codec; + +import common.net.buffer.ByteBuf; +import common.net.buffer.Unpooled; +import common.net.channel.ChannelHandlerContext; +import common.net.channel.ChannelOutboundHandlerAdapter; +import common.net.channel.ChannelPromise; +import common.net.util.ReferenceCountUtil; +import common.net.util.internal.TypeParameterMatcher; + + +/** + * {@link ChannelOutboundHandlerAdapter} which encodes message in a stream-like fashion from one message to an + * {@link ByteBuf}. + * + * + * Example implementation which encodes {@link Integer}s to a {@link ByteBuf}. + * + *
+ *     public class IntegerEncoder extends {@link MessageToByteEncoder}<{@link Integer}> {
+ *         {@code @Override}
+ *         public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out)
+ *                 throws {@link Exception} {
+ *             out.writeInt(msg);
+ *         }
+ *     }
+ * 
+ */ +public abstract class MessageToByteEncoder extends ChannelOutboundHandlerAdapter { + + private final TypeParameterMatcher matcher; + private final boolean preferDirect; + + /** + * @see {@link #MessageToByteEncoder(boolean)} with {@code true} as boolean parameter. + */ + protected MessageToByteEncoder() { + this(true); + } + + /** + * @see {@link #MessageToByteEncoder(Class, boolean)} with {@code true} as boolean value. + */ + protected MessageToByteEncoder(Class outboundMessageType) { + this(outboundMessageType, true); + } + + /** + * Create a new instance which will try to detect the types to match out of the type parameter of the class. + * + * @param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for + * the encoded messages. If {@code false} is used it will allocate a heap + * {@link ByteBuf}, which is backed by an byte array. + */ + protected MessageToByteEncoder(boolean preferDirect) { + matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I"); + this.preferDirect = preferDirect; + } + + /** + * Create a new instance + * + * @param outboundMessageType The tpye of messages to match + * @param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for + * the encoded messages. If {@code false} is used it will allocate a heap + * {@link ByteBuf}, which is backed by an byte array. + */ + protected MessageToByteEncoder(Class outboundMessageType, boolean preferDirect) { + matcher = TypeParameterMatcher.get(outboundMessageType); + this.preferDirect = preferDirect; + } + + /** + * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next + * {@link ChannelOutboundHandler} in the {@link ChannelPipeline}. + */ + public boolean acceptOutboundMessage(Object msg) throws Exception { + return matcher.match(msg); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + ByteBuf buf = null; + try { + if (acceptOutboundMessage(msg)) { + + I cast = (I) msg; + buf = allocateBuffer(ctx, cast, preferDirect); + try { + encode(ctx, cast, buf); + } finally { + ReferenceCountUtil.release(cast); + } + + if (buf.isReadable()) { + ctx.write(buf, promise); + } else { + buf.release(); + ctx.write(Unpooled.EMPTY_BUFFER, promise); + } + buf = null; + } else { + ctx.write(msg, promise); + } + } catch (EncoderException e) { + throw e; + } catch (Throwable e) { + throw new EncoderException(e); + } finally { + if (buf != null) { + buf.release(); + } + } + } + + /** + * Allocate a {@link ByteBuf} which will be used as argument of {@link #encode(ChannelHandlerContext, I, ByteBuf)}. + * Sub-classes may override this method to returna {@link ByteBuf} with a perfect matching {@code initialCapacity}. + */ + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, I msg, + boolean preferDirect) throws Exception { + if (preferDirect) { + return ctx.alloc().ioBuffer(); + } else { + return ctx.alloc().heapBuffer(); + } + } + + /** + * Encode a message into a {@link ByteBuf}. This method will be called for each written message that can be handled + * by this encoder. + * + * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to + * @param msg the message to encode + * @param out the {@link ByteBuf} into which the encoded message will be written + * @throws Exception is thrown if an error accour + */ + protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception; +} diff --git a/common/src/common/net/handler/codec/MessageToMessageDecoder.java b/common/src/common/net/handler/codec/MessageToMessageDecoder.java new file mode 100644 index 0000000..6b6c2eb --- /dev/null +++ b/common/src/common/net/handler/codec/MessageToMessageDecoder.java @@ -0,0 +1,115 @@ +/* + * 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 common.net.handler.codec; + +import java.util.List; + +import common.net.channel.ChannelHandlerContext; +import common.net.channel.ChannelInboundHandlerAdapter; +import common.net.util.ReferenceCountUtil; +import common.net.util.internal.RecyclableArrayList; +import common.net.util.internal.TypeParameterMatcher; + +/** + * {@link ChannelInboundHandlerAdapter} which decodes from one message to an other message. + * + * + * For example here is an implementation which decodes a {@link String} to an {@link Integer} which represent + * the length of the {@link String}. + * + *
+ *     public class StringToIntegerDecoder extends
+ *             {@link MessageToMessageDecoder}<{@link String}> {
+ *
+ *         {@code @Override}
+ *         public void decode({@link ChannelHandlerContext} ctx, {@link String} message,
+ *                            List<Object> out) throws {@link Exception} {
+ *             out.add(message.length());
+ *         }
+ *     }
+ * 
+ * + * Be aware that you need to call {@link ReferenceCounted#retain()} on messages that are just passed through if they + * are of type {@link ReferenceCounted}. This is needed as the {@link MessageToMessageDecoder} will call + * {@link ReferenceCounted#release()} on decoded messages. + * + */ +public abstract class MessageToMessageDecoder extends ChannelInboundHandlerAdapter { + + private final TypeParameterMatcher matcher; + + /** + * Create a new instance which will try to detect the types to match out of the type parameter of the class. + */ + protected MessageToMessageDecoder() { + matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I"); + } + + /** + * Create a new instance + * + * @param inboundMessageType The type of messages to match and so decode + */ + protected MessageToMessageDecoder(Class inboundMessageType) { + matcher = TypeParameterMatcher.get(inboundMessageType); + } + + /** + * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next + * {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + */ + public boolean acceptInboundMessage(Object msg) throws Exception { + return matcher.match(msg); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + RecyclableArrayList out = RecyclableArrayList.newInstance(); + try { + if (acceptInboundMessage(msg)) { + I cast = (I) msg; + try { + decode(ctx, cast, out); + } finally { + ReferenceCountUtil.release(cast); + } + } else { + out.add(msg); + } + } catch (DecoderException e) { + throw e; + } catch (Exception e) { + throw new DecoderException(e); + } finally { + int size = out.size(); + for (int i = 0; i < size; i ++) { + ctx.fireChannelRead(out.get(i)); + } + out.recycle(); + } + } + + /** + * Decode from one message to an other. This method will be called for each written message that can be handled + * by this encoder. + * + * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToMessageDecoder} belongs to + * @param msg the message to decode to an other one + * @param out the {@link List} to which decoded messages should be added + * @throws Exception is thrown if an error accour + */ + protected abstract void decode(ChannelHandlerContext ctx, I msg, List out) throws Exception; +} diff --git a/common/src/common/net/handler/timeout/ReadTimeoutException.java b/common/src/common/net/handler/timeout/ReadTimeoutException.java new file mode 100644 index 0000000..3216773 --- /dev/null +++ b/common/src/common/net/handler/timeout/ReadTimeoutException.java @@ -0,0 +1,29 @@ +/* + * 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 common.net.handler.timeout; + +/** + * A {@link TimeoutException} raised by {@link ReadTimeoutHandler} when no data + * was read within a certain period of time. + */ +public final class ReadTimeoutException extends TimeoutException { + + private static final long serialVersionUID = 169287984113283421L; + + public static final ReadTimeoutException INSTANCE = new ReadTimeoutException(); + + private ReadTimeoutException() { } +} diff --git a/common/src/common/net/handler/timeout/ReadTimeoutHandler.java b/common/src/common/net/handler/timeout/ReadTimeoutHandler.java new file mode 100644 index 0000000..d21019c --- /dev/null +++ b/common/src/common/net/handler/timeout/ReadTimeoutHandler.java @@ -0,0 +1,218 @@ +/* + * 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 common.net.handler.timeout; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import common.net.channel.ChannelHandlerContext; +import common.net.channel.ChannelInboundHandlerAdapter; + +/** + * Raises a {@link ReadTimeoutException} when no data was read within a certain + * period of time. + * + *
+ * // The connection is closed when there is no inbound traffic
+ * // for 30 seconds.
+ *
+ * public class MyChannelInitializer extends {@link ChannelInitializer}<{@link Channel}> {
+ *     public void initChannel({@link Channel} channel) {
+ *         channel.pipeline().addLast("readTimeoutHandler", new {@link ReadTimeoutHandler}(30);
+ *         channel.pipeline().addLast("myHandler", new MyHandler());
+ *     }
+ * }
+ *
+ * // Handler should handle the {@link ReadTimeoutException}.
+ * public class MyHandler extends {@link ChannelDuplexHandler} {
+ *     {@code @Override}
+ *     public void exceptionCaught({@link ChannelHandlerContext} ctx, {@link Throwable} cause)
+ *             throws {@link Exception} {
+ *         if (cause instanceof {@link ReadTimeoutException}) {
+ *             // do something
+ *         } else {
+ *             super.exceptionCaught(ctx, cause);
+ *         }
+ *     }
+ * }
+ *
+ * {@link ServerBootstrap} bootstrap = ...;
+ * ...
+ * bootstrap.childHandler(new MyChannelInitializer());
+ * ...
+ * 
+ * @see WriteTimeoutHandler + * @see IdleStateHandler + */ +public class ReadTimeoutHandler extends ChannelInboundHandlerAdapter { + private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1); + + private final long timeoutNanos; + + private volatile ScheduledFuture timeout; + private volatile long lastReadTime; + + private volatile int state; // 0 - none, 1 - Initialized, 2 - Destroyed; + + private boolean closed; + + /** + * Creates a new instance. + * + * @param timeoutSeconds + * read timeout in seconds + */ + public ReadTimeoutHandler(int timeoutSeconds) { + this(timeoutSeconds, TimeUnit.SECONDS); + } + + /** + * Creates a new instance. + * + * @param timeout + * read timeout + * @param unit + * the {@link TimeUnit} of {@code timeout} + */ + public ReadTimeoutHandler(long timeout, TimeUnit unit) { + if (unit == null) { + throw new NullPointerException("unit"); + } + + if (timeout <= 0) { + timeoutNanos = 0; + } else { + timeoutNanos = Math.max(unit.toNanos(timeout), MIN_TIMEOUT_NANOS); + } + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + if (ctx.channel().isActive() && ctx.channel().isRegistered()) { + // channelActvie() event has been fired already, which means this.channelActive() will + // not be invoked. We have to initialize here instead. + initialize(ctx); + } else { + // channelActive() event has not been fired yet. this.channelActive() will be invoked + // and initialization will occur there. + } + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + destroy(); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + // Initialize early if channel is active already. + if (ctx.channel().isActive()) { + initialize(ctx); + } + super.channelRegistered(ctx); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + // This method will be invoked only if this handler was added + // before channelActive() event is fired. If a user adds this handler + // after the channelActive() event, initialize() will be called by beforeAdd(). + initialize(ctx); + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + destroy(); + super.channelInactive(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + lastReadTime = System.nanoTime(); + ctx.fireChannelRead(msg); + } + + private void initialize(ChannelHandlerContext ctx) { + // Avoid the case where destroy() is called before scheduling timeouts. + // See: https://github.com/netty/netty/issues/143 + switch (state) { + case 1: + case 2: + return; + } + + state = 1; + + lastReadTime = System.nanoTime(); + if (timeoutNanos > 0) { + timeout = ctx.executor().schedule( + new ReadTimeoutTask(ctx), + timeoutNanos, TimeUnit.NANOSECONDS); + } + } + + private void destroy() { + state = 2; + + if (timeout != null) { + timeout.cancel(false); + timeout = null; + } + } + + /** + * Is called when a read timeout was detected. + */ + protected void readTimedOut(ChannelHandlerContext ctx) throws Exception { + if (!closed) { + ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE); + ctx.close(); + closed = true; + } + } + + private final class ReadTimeoutTask implements Runnable { + + private final ChannelHandlerContext ctx; + + ReadTimeoutTask(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + @Override + public void run() { + if (!ctx.channel().isOpen()) { + return; + } + + long currentTime = System.nanoTime(); + long nextDelay = timeoutNanos - (currentTime - lastReadTime); + if (nextDelay <= 0) { + // Read timed out - set a new timeout and notify the callback. + timeout = ctx.executor().schedule(this, timeoutNanos, TimeUnit.NANOSECONDS); + try { + readTimedOut(ctx); + } catch (Throwable t) { + ctx.fireExceptionCaught(t); + } + } else { + // Read occurred before the timeout - set a new timeout with shorter delay. + timeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS); + } + } + } +} diff --git a/common/src/common/net/handler/timeout/TimeoutException.java b/common/src/common/net/handler/timeout/TimeoutException.java new file mode 100644 index 0000000..491f161 --- /dev/null +++ b/common/src/common/net/handler/timeout/TimeoutException.java @@ -0,0 +1,34 @@ +/* + * 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 common.net.handler.timeout; + +import common.net.channel.ChannelException; + +/** + * A {@link TimeoutException} when no data was either read or written within a + * certain period of time. + */ +public class TimeoutException extends ChannelException { + + private static final long serialVersionUID = 4673641882869672533L; + + TimeoutException() { } + + @Override + public Throwable fillInStackTrace() { + return this; + } +} diff --git a/common/src/common/net/util/Attribute.java b/common/src/common/net/util/Attribute.java new file mode 100644 index 0000000..56075ee --- /dev/null +++ b/common/src/common/net/util/Attribute.java @@ -0,0 +1,67 @@ +/* + * 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 common.net.util; + +/** + * An attribute which allows to store a value reference. It may be updated atomically and so is thread-safe. + * + * @param the type of the value it holds. + */ +public interface Attribute { + + /** + * Returns the key of this attribute. + */ + AttributeKey key(); + + /** + * Returns the current value, which may be {@code null} + */ + T get(); + + /** + * Sets the value + */ + void set(T value); + + /** + * Atomically sets to the given value and returns the old value which may be {@code null} if non was set before. + */ + T getAndSet(T value); + + /** + * Atomically sets to the given value if this {@link Attribute} does not contain a value at the moment. + * If it was not possible to set the value as it contains a value it will just return the current value. + */ + T setIfAbsent(T value); + + /** + * Removes this attribute from the {@link AttributeMap} and returns the old value.. Subsequent {@link #get()} + * calls will return @{code null}. + */ + T getAndRemove(); + + /** + * Atomically sets the value to the given updated value if the current value == the expected value. + * If it the set was successful it returns {@code true} otherwise {@code false}. + */ + boolean compareAndSet(T oldValue, T newValue); + + /** + * Removes this attribute from the {@link AttributeMap}. Subsequent {@link #get()} calls will return @{code null}. + */ + void remove(); +} diff --git a/common/src/common/net/util/AttributeKey.java b/common/src/common/net/util/AttributeKey.java new file mode 100644 index 0000000..9db2217 --- /dev/null +++ b/common/src/common/net/util/AttributeKey.java @@ -0,0 +1,49 @@ +/* + * 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 common.net.util; + +import java.util.concurrent.ConcurrentMap; + +import common.net.util.internal.PlatformDependent; + +/** + * Key which can be used to access {@link Attribute} out of the {@link AttributeMap}. Be aware that it is not be + * possible to have multiple keys with the same name. + * + * + * @param the type of the {@link Attribute} which can be accessed via this {@link AttributeKey}. + */ + // 'T' is used only at compile time +public final class AttributeKey extends UniqueName { + + private static final ConcurrentMap names = PlatformDependent.newConcurrentHashMap(); + + /** + * Creates a new {@link AttributeKey} with the specified {@code name}. + */ + + public static AttributeKey valueOf(String name) { + return new AttributeKey(name); + } + + /** + * @deprecated Use {@link #valueOf(String)} instead. + */ + @Deprecated + public AttributeKey(String name) { + super(names, name); + } +} diff --git a/common/src/common/net/util/AttributeMap.java b/common/src/common/net/util/AttributeMap.java new file mode 100644 index 0000000..2a1e85a --- /dev/null +++ b/common/src/common/net/util/AttributeMap.java @@ -0,0 +1,29 @@ +/* + * 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 common.net.util; + +/** + * Holds {@link Attribute}s which can be accessed via {@link AttributeKey}. + * + * Implementations must be Thread-safe. + */ +public interface AttributeMap { + /** + * Get the {@link Attribute} for the given {@link AttributeKey}. This method will never return null, but may return + * an {@link Attribute} which does not have a value set yet. + */ + Attribute attr(AttributeKey key); +} diff --git a/common/src/common/net/util/CharsetUtil.java b/common/src/common/net/util/CharsetUtil.java new file mode 100644 index 0000000..5e69ad3 --- /dev/null +++ b/common/src/common/net/util/CharsetUtil.java @@ -0,0 +1,117 @@ +/* + * 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 common.net.util; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.Map; + +import common.net.util.internal.InternalThreadLocalMap; + +/** + * A utility class that provides various common operations and constants + * related with {@link Charset} and its relevant classes. + */ +public final class CharsetUtil { + + /** + * 16-bit UTF (UCS Transformation Format) whose byte order is identified by + * an optional byte-order mark + */ + public static final Charset UTF_16 = Charset.forName("UTF-16"); + + /** + * 16-bit UTF (UCS Transformation Format) whose byte order is big-endian + */ + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + + /** + * 16-bit UTF (UCS Transformation Format) whose byte order is little-endian + */ + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + + /** + * 8-bit UTF (UCS Transformation Format) + */ + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** + * ISO Latin Alphabet No. 1, as known as ISO-LATIN-1 + */ + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + /** + * 7-bit ASCII, as known as ISO646-US or the Basic Latin block of the + * Unicode character set + */ + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + /** + * Returns a cached thread-local {@link CharsetEncoder} for the specified + * charset. + */ + public static CharsetEncoder getEncoder(Charset charset) { + if (charset == null) { + throw new NullPointerException("charset"); + } + + Map map = InternalThreadLocalMap.get().charsetEncoderCache(); + CharsetEncoder e = map.get(charset); + if (e != null) { + e.reset(); + e.onMalformedInput(CodingErrorAction.REPLACE); + e.onUnmappableCharacter(CodingErrorAction.REPLACE); + return e; + } + + e = charset.newEncoder(); + e.onMalformedInput(CodingErrorAction.REPLACE); + e.onUnmappableCharacter(CodingErrorAction.REPLACE); + map.put(charset, e); + return e; + } + + /** + * Returns a cached thread-local {@link CharsetDecoder} for the specified + * charset. + */ + public static CharsetDecoder getDecoder(Charset charset) { + if (charset == null) { + throw new NullPointerException("charset"); + } + + Map map = InternalThreadLocalMap.get().charsetDecoderCache(); + CharsetDecoder d = map.get(charset); + if (d != null) { + d.reset(); + d.onMalformedInput(CodingErrorAction.REPLACE); + d.onUnmappableCharacter(CodingErrorAction.REPLACE); + return d; + } + + d = charset.newDecoder(); + d.onMalformedInput(CodingErrorAction.REPLACE); + d.onUnmappableCharacter(CodingErrorAction.REPLACE); + map.put(charset, d); + return d; + } + + private CharsetUtil() { + // Unused + } +} diff --git a/common/src/common/net/util/DefaultAttributeMap.java b/common/src/common/net/util/DefaultAttributeMap.java new file mode 100644 index 0000000..605849a --- /dev/null +++ b/common/src/common/net/util/DefaultAttributeMap.java @@ -0,0 +1,179 @@ +/* + * 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 common.net.util; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import common.net.util.internal.PlatformDependent; + +/** + * Default {@link AttributeMap} implementation which use simple synchronization per bucket to keep the memory overhead + * as low as possible. + */ +public class DefaultAttributeMap implements AttributeMap { + + + private static final AtomicReferenceFieldUpdater updater; + + static { + + AtomicReferenceFieldUpdater referenceFieldUpdater = + PlatformDependent.newAtomicReferenceFieldUpdater(DefaultAttributeMap.class, "attributes"); + if (referenceFieldUpdater == null) { + referenceFieldUpdater = AtomicReferenceFieldUpdater + .newUpdater(DefaultAttributeMap.class, AtomicReferenceArray.class, "attributes"); + } + updater = referenceFieldUpdater; + } + + private static final int BUCKET_SIZE = 4; + private static final int MASK = BUCKET_SIZE - 1; + + // Initialize lazily to reduce memory consumption; updated by AtomicReferenceFieldUpdater above. + + private volatile AtomicReferenceArray> attributes; + + + @Override + public Attribute attr(AttributeKey key) { + if (key == null) { + throw new NullPointerException("key"); + } + AtomicReferenceArray> attributes = this.attributes; + if (attributes == null) { + // Not using ConcurrentHashMap due to high memory consumption. + attributes = new AtomicReferenceArray>(BUCKET_SIZE); + + if (!updater.compareAndSet(this, null, attributes)) { + attributes = this.attributes; + } + } + + int i = index(key); + DefaultAttribute head = attributes.get(i); + if (head == null) { + // No head exists yet which means we may be able to add the attribute without synchronization and just + // use compare and set. At worst we need to fallback to synchronization + head = new DefaultAttribute(key); + if (attributes.compareAndSet(i, null, head)) { + // we were able to add it so return the head right away + return (Attribute) head; + } else { + head = attributes.get(i); + } + } + + synchronized (head) { + DefaultAttribute curr = head; + for (;;) { + if (!curr.removed && curr.key == key) { + return (Attribute) curr; + } + + DefaultAttribute next = curr.next; + if (next == null) { + DefaultAttribute attr = new DefaultAttribute(head, key); + curr.next = attr; + attr.prev = curr; + return attr; + } else { + curr = next; + } + } + } + } + + private static int index(AttributeKey key) { + return key.id() & MASK; + } + + + private static final class DefaultAttribute extends AtomicReference implements Attribute { + + private static final long serialVersionUID = -2661411462200283011L; + + // The head of the linked-list this attribute belongs to, which may be itself + private final DefaultAttribute head; + private final AttributeKey key; + + // Double-linked list to prev and next node to allow fast removal + private DefaultAttribute prev; + private DefaultAttribute next; + + // Will be set to true one the attribute is removed via getAndRemove() or remove() + private volatile boolean removed; + + DefaultAttribute(DefaultAttribute head, AttributeKey key) { + this.head = head; + this.key = key; + } + + DefaultAttribute(AttributeKey key) { + head = this; + this.key = key; + } + + @Override + public AttributeKey key() { + return key; + } + + @Override + public T setIfAbsent(T value) { + while (!compareAndSet(null, value)) { + T old = get(); + if (old != null) { + return old; + } + } + return null; + } + + @Override + public T getAndRemove() { + removed = true; + T oldValue = getAndSet(null); + remove0(); + return oldValue; + } + + @Override + public void remove() { + removed = true; + set(null); + remove0(); + } + + private void remove0() { + synchronized (head) { + // We only update the linked-list structure if prev != null because if it is null this + // DefaultAttribute acts also as head. The head must never be removed completely and just be + // marked as removed as all synchronization is done on the head itself for each bucket. + // The head itself will be GC'ed once the DefaultAttributeMap is GC'ed. So at most 5 heads will + // be removed lazy as the array size is 5. + if (prev != null) { + prev.next = next; + + if (next != null) { + next.prev = prev; + } + } + } + } + } +} diff --git a/common/src/common/net/util/IllegalReferenceCountException.java b/common/src/common/net/util/IllegalReferenceCountException.java new file mode 100644 index 0000000..eed5aa7 --- /dev/null +++ b/common/src/common/net/util/IllegalReferenceCountException.java @@ -0,0 +1,48 @@ +/* + * 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 common.net.util; + +/** + * An {@link IllegalStateException} which is raised when a user attempts to access a {@link ReferenceCounted} whose + * reference count has been decreased to 0 (and consequently freed). + */ +public class IllegalReferenceCountException extends IllegalStateException { + + private static final long serialVersionUID = -2507492394288153468L; + + public IllegalReferenceCountException() { } + + public IllegalReferenceCountException(int refCnt) { + this("refCnt: " + refCnt); + } + + public IllegalReferenceCountException(int refCnt, int increment) { + this("refCnt: " + refCnt + ", " + (increment > 0? "increment: " + increment : "decrement: " + -increment)); + } + + public IllegalReferenceCountException(String message) { + super(message); + } + + public IllegalReferenceCountException(String message, Throwable cause) { + super(message, cause); + } + + public IllegalReferenceCountException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/common/net/util/NetUtil.java b/common/src/common/net/util/NetUtil.java new file mode 100644 index 0000000..0fd6f0e --- /dev/null +++ b/common/src/common/net/util/NetUtil.java @@ -0,0 +1,614 @@ +/* + * 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 common.net.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; + +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A class that holds a number of network-related constants. + *

+ * This class borrowed some of its methods from a modified fork of the + * Inet6Util class which was part of Apache Harmony. + */ +public final class NetUtil { + + /** + * The {@link Inet4Address} that represents the IPv4 loopback address '127.0.0.1' + */ + public static final Inet4Address LOCALHOST4; + + /** + * The {@link Inet6Address} that represents the IPv6 loopback address '::1' + */ + public static final Inet6Address LOCALHOST6; + + /** + * The {@link InetAddress} that represents the loopback address. If IPv6 stack is available, it will refer to + * {@link #LOCALHOST6}. Otherwise, {@link #LOCALHOST4}. + */ + public static final InetAddress LOCALHOST; + + /** + * The loopback {@link NetworkInterface} of the current machine + */ + public static final NetworkInterface LOOPBACK_IF; + + /** + * The SOMAXCONN value of the current machine. If failed to get the value, 3072 is used as a + * default value. + */ + public static final int SOMAXCONN; + + /** + * The logger being used by this class + */ + private static final InternalLogger logger = InternalLoggerFactory.getInstance(NetUtil.class); + + static { + byte[] LOCALHOST4_BYTES = {127, 0, 0, 1}; + byte[] LOCALHOST6_BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + + // Create IPv4 loopback address. + Inet4Address localhost4 = null; + try { + localhost4 = (Inet4Address) InetAddress.getByAddress(LOCALHOST4_BYTES); + } catch (Exception e) { + // We should not get here as long as the length of the address is correct. + PlatformDependent.throwException(e); + } + LOCALHOST4 = localhost4; + + // Create IPv6 loopback address. + Inet6Address localhost6 = null; + try { + localhost6 = (Inet6Address) InetAddress.getByAddress(LOCALHOST6_BYTES); + } catch (Exception e) { + // We should not get here as long as the length of the address is correct. + PlatformDependent.throwException(e); + } + LOCALHOST6 = localhost6; + + // Retrieve the list of available network interfaces. + List ifaces = new ArrayList(); + try { + for (Enumeration i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) { + NetworkInterface iface = i.nextElement(); + // Use the interface with proper INET addresses only. + if (iface.getInetAddresses().hasMoreElements()) { + ifaces.add(iface); + } + } + } catch (SocketException e) { + logger.warn("Failed to retrieve the list of available network interfaces", e); + } + + // Find the first loopback interface available from its INET address (127.0.0.1 or ::1) + // Note that we do not use NetworkInterface.isLoopback() in the first place because it takes long time + // on a certain environment. (e.g. Windows with -Djava.net.preferIPv4Stack=true) + NetworkInterface loopbackIface = null; + InetAddress loopbackAddr = null; + loop: for (NetworkInterface iface: ifaces) { + for (Enumeration i = iface.getInetAddresses(); i.hasMoreElements();) { + InetAddress addr = i.nextElement(); + if (addr.isLoopbackAddress()) { + // Found + loopbackIface = iface; + loopbackAddr = addr; + break loop; + } + } + } + + // If failed to find the loopback interface from its INET address, fall back to isLoopback(). + if (loopbackIface == null) { + try { + for (NetworkInterface iface: ifaces) { + if (iface.isLoopback()) { + Enumeration i = iface.getInetAddresses(); + if (i.hasMoreElements()) { + // Found the one with INET address. + loopbackIface = iface; + loopbackAddr = i.nextElement(); + break; + } + } + } + + if (loopbackIface == null) { + logger.warn("Failed to find the loopback interface"); + } + } catch (SocketException e) { + logger.warn("Failed to find the loopback interface", e); + } + } + + if (loopbackIface != null) { + // Found the loopback interface with an INET address. + logger.debug( + "Loopback interface: {} ({}, {})", + loopbackIface.getName(), loopbackIface.getDisplayName(), loopbackAddr.getHostAddress()); + } else { + // Could not find the loopback interface, but we can't leave LOCALHOST as null. + // Use LOCALHOST6 or LOCALHOST4, preferably the IPv6 one. + if (loopbackAddr == null) { + try { + if (NetworkInterface.getByInetAddress(LOCALHOST6) != null) { + logger.debug("Using hard-coded IPv6 localhost address: {}", localhost6); + loopbackAddr = localhost6; + } + } catch (Exception e) { + // Ignore + } finally { + if (loopbackAddr == null) { + logger.debug("Using hard-coded IPv4 localhost address: {}", localhost4); + loopbackAddr = localhost4; + } + } + } + } + + LOOPBACK_IF = loopbackIface; + LOCALHOST = loopbackAddr; + + // Determine the default somaxconn (server socket backlog) value of the platform. + // The known defaults: + // - Windows NT Server 4.0+: 200 + // - Linux and Mac OS X: 128 + int somaxconn = PlatformDependent.isWindows() ? 200 : 128; + File file = new File("/proc/sys/net/core/somaxconn"); + if (file.exists()) { + BufferedReader in = null; + try { + in = new BufferedReader(new FileReader(file)); + somaxconn = Integer.parseInt(in.readLine()); + if (logger.isDebugEnabled()) { + logger.debug("{}: {}", file, somaxconn); + } + } catch (Exception e) { + logger.debug("Failed to get SOMAXCONN from: {}", file, e); + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception e) { + // Ignored. + } + } + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("{}: {} (non-existent)", file, somaxconn); + } + } + + SOMAXCONN = somaxconn; + } + + /** + * Creates an byte[] based on an ipAddressString. No error handling is + * performed here. + */ + public static byte[] createByteArrayFromIpAddressString(String ipAddressString) { + + if (isValidIpV4Address(ipAddressString)) { + StringTokenizer tokenizer = new StringTokenizer(ipAddressString, "."); + String token; + int tempInt; + byte[] byteAddress = new byte[4]; + for (int i = 0; i < 4; i ++) { + token = tokenizer.nextToken(); + tempInt = Integer.parseInt(token); + byteAddress[i] = (byte) tempInt; + } + + return byteAddress; + } + + if (isValidIpV6Address(ipAddressString)) { + if (ipAddressString.charAt(0) == '[') { + ipAddressString = ipAddressString.substring(1, ipAddressString.length() - 1); + } + + StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ":.", true); + ArrayList hexStrings = new ArrayList(); + ArrayList decStrings = new ArrayList(); + String token = ""; + String prevToken = ""; + int doubleColonIndex = -1; // If a double colon exists, we need to + // insert 0s. + + // Go through the tokens, including the seperators ':' and '.' + // When we hit a : or . the previous token will be added to either + // the hex list or decimal list. In the case where we hit a :: + // we will save the index of the hexStrings so we can add zeros + // in to fill out the string + while (tokenizer.hasMoreTokens()) { + prevToken = token; + token = tokenizer.nextToken(); + + if (":".equals(token)) { + if (":".equals(prevToken)) { + doubleColonIndex = hexStrings.size(); + } else if (!prevToken.isEmpty()) { + hexStrings.add(prevToken); + } + } else if (".".equals(token)) { + decStrings.add(prevToken); + } + } + + if (":".equals(prevToken)) { + if (":".equals(token)) { + doubleColonIndex = hexStrings.size(); + } else { + hexStrings.add(token); + } + } else if (".".equals(prevToken)) { + decStrings.add(token); + } + + // figure out how many hexStrings we should have + // also check if it is a IPv4 address + int hexStringsLength = 8; + + // If we have an IPv4 address tagged on at the end, subtract + // 4 bytes, or 2 hex words from the total + if (!decStrings.isEmpty()) { + hexStringsLength -= 2; + } + + // if we hit a double Colon add the appropriate hex strings + if (doubleColonIndex != -1) { + int numberToInsert = hexStringsLength - hexStrings.size(); + for (int i = 0; i < numberToInsert; i ++) { + hexStrings.add(doubleColonIndex, "0"); + } + } + + byte[] ipByteArray = new byte[16]; + + // Finally convert these strings to bytes... + for (int i = 0; i < hexStrings.size(); i ++) { + convertToBytes(hexStrings.get(i), ipByteArray, i * 2); + } + + // Now if there are any decimal values, we know where they go... + for (int i = 0; i < decStrings.size(); i ++) { + ipByteArray[i + 12] = (byte) (Integer.parseInt(decStrings.get(i)) & 255); + } + return ipByteArray; + } + return null; + } + + /** + * Converts a 4 character hex word into a 2 byte word equivalent + */ + private static void convertToBytes(String hexWord, byte[] ipByteArray, int byteIndex) { + + int hexWordLength = hexWord.length(); + int hexWordIndex = 0; + ipByteArray[byteIndex] = 0; + ipByteArray[byteIndex + 1] = 0; + int charValue; + + // high order 4 bits of first byte + if (hexWordLength > 3) { + charValue = getIntValue(hexWord.charAt(hexWordIndex ++)); + ipByteArray[byteIndex] |= charValue << 4; + } + + // low order 4 bits of the first byte + if (hexWordLength > 2) { + charValue = getIntValue(hexWord.charAt(hexWordIndex ++)); + ipByteArray[byteIndex] |= charValue; + } + + // high order 4 bits of second byte + if (hexWordLength > 1) { + charValue = getIntValue(hexWord.charAt(hexWordIndex ++)); + ipByteArray[byteIndex + 1] |= charValue << 4; + } + + // low order 4 bits of the first byte + charValue = getIntValue(hexWord.charAt(hexWordIndex)); + ipByteArray[byteIndex + 1] |= charValue & 15; + } + + static int getIntValue(char c) { + + switch (c) { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + } + + c = Character.toLowerCase(c); + switch (c) { + case 'a': + return 10; + case 'b': + return 11; + case 'c': + return 12; + case 'd': + return 13; + case 'e': + return 14; + case 'f': + return 15; + } + return 0; + } + + public static boolean isValidIpV6Address(String ipAddress) { + int length = ipAddress.length(); + boolean doubleColon = false; + int numberOfColons = 0; + int numberOfPeriods = 0; + int numberOfPercent = 0; + StringBuilder word = new StringBuilder(); + char c = 0; + char prevChar; + int offset = 0; // offset for [] ip addresses + + if (length < 2) { + return false; + } + + for (int i = 0; i < length; i ++) { + prevChar = c; + c = ipAddress.charAt(i); + switch (c) { + + // case for an open bracket [x:x:x:...x] + case '[': + if (i != 0) { + return false; // must be first character + } + if (ipAddress.charAt(length - 1) != ']') { + return false; // must have a close ] + } + offset = 1; + if (length < 4) { + return false; + } + break; + + // case for a closed bracket at end of IP [x:x:x:...x] + case ']': + if (i != length - 1) { + return false; // must be last charcter + } + if (ipAddress.charAt(0) != '[') { + return false; // must have a open [ + } + break; + + // case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d + case '.': + numberOfPeriods ++; + if (numberOfPeriods > 3) { + return false; + } + if (!isValidIp4Word(word.toString())) { + return false; + } + if (numberOfColons != 6 && !doubleColon) { + return false; + } + // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an + // IPv4 ending, otherwise 7 :'s is bad + if (numberOfColons == 7 && ipAddress.charAt(offset) != ':' && + ipAddress.charAt(1 + offset) != ':') { + return false; + } + word.delete(0, word.length()); + break; + + case ':': + // FIX "IP6 mechanism syntax #ip6-bad1" + // An IPV6 address cannot start with a single ":". + // Either it can starti with "::" or with a number. + if (i == offset && (ipAddress.length() <= i || ipAddress.charAt(i + 1) != ':')) { + return false; + } + // END FIX "IP6 mechanism syntax #ip6-bad1" + numberOfColons ++; + if (numberOfColons > 7) { + return false; + } + if (numberOfPeriods > 0) { + return false; + } + if (prevChar == ':') { + if (doubleColon) { + return false; + } + doubleColon = true; + } + word.delete(0, word.length()); + break; + case '%': + if (numberOfColons == 0) { + return false; + } + numberOfPercent ++; + + // validate that the stuff after the % is valid + if (i + 1 >= length) { + // in this case the percent is there but no number is + // available + return false; + } + try { + if (Integer.parseInt(ipAddress.substring(i + 1)) < 0) { + return false; + } + } catch (NumberFormatException e) { + // right now we just support an integer after the % so if + // this is not + // what is there then return + return false; + } + break; + + default: + if (numberOfPercent == 0) { + if (word != null && word.length() > 3) { + return false; + } + if (!isValidHexChar(c)) { + return false; + } + } + word.append(c); + } + } + + // Check if we have an IPv4 ending + if (numberOfPeriods > 0) { + // There is a test case with 7 colons and valid ipv4 this should resolve it + if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && numberOfColons < 7)) { + return false; + } + } else { + // If we're at then end and we haven't had 7 colons then there is a + // problem unless we encountered a doubleColon + if (numberOfColons != 7 && !doubleColon) { + return false; + } + + // If we have an empty word at the end, it means we ended in either + // a : or a . + // If we did not end in :: then this is invalid + if (numberOfPercent == 0) { + if (word.length() == 0 && ipAddress.charAt(length - 1 - offset) == ':' && + ipAddress.charAt(length - 2 - offset) != ':') { + return false; + } + } + } + + return true; + } + + public static boolean isValidIp4Word(String word) { + char c; + if (word.length() < 1 || word.length() > 3) { + return false; + } + for (int i = 0; i < word.length(); i ++) { + c = word.charAt(i); + if (!(c >= '0' && c <= '9')) { + return false; + } + } + return Integer.parseInt(word) <= 255; + } + + static boolean isValidHexChar(char c) { + return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f'; + } + + /** + * Takes a string and parses it to see if it is a valid IPV4 address. + * + * @return true, if the string represents an IPV4 address in dotted + * notation, false otherwise + */ + public static boolean isValidIpV4Address(String value) { + + int periods = 0; + int i; + int length = value.length(); + + if (length > 15) { + return false; + } + char c; + StringBuilder word = new StringBuilder(); + for (i = 0; i < length; i ++) { + c = value.charAt(i); + if (c == '.') { + periods ++; + if (periods > 3) { + return false; + } + if (word.length() == 0) { + return false; + } + if (Integer.parseInt(word.toString()) > 255) { + return false; + } + word.delete(0, word.length()); + } else if (!Character.isDigit(c)) { + return false; + } else { + if (word.length() > 2) { + return false; + } + word.append(c); + } + } + + if (word.length() == 0 || Integer.parseInt(word.toString()) > 255) { + return false; + } + + return periods == 3; + } + + /** + * A constructor to stop this class being constructed. + */ + private NetUtil() { + // Unused + } +} diff --git a/common/src/common/net/util/Recycler.java b/common/src/common/net/util/Recycler.java new file mode 100644 index 0000000..9fb120d --- /dev/null +++ b/common/src/common/net/util/Recycler.java @@ -0,0 +1,353 @@ +/* + * Copyright 2013 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 common.net.util; + +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import common.net.util.concurrent.FastThreadLocal; +import common.net.util.internal.SystemPropertyUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Light-weight object pool based on a thread-local stack. + * + * @param the type of the pooled object + */ +public abstract class Recycler { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(Recycler.class); + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE); + private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement(); + private static final int DEFAULT_MAX_CAPACITY; + private static final int INITIAL_CAPACITY; + + static { + // In the future, we might have different maxCapacity for different object types. + // e.g. game.net.recycler.maxCapacity.writeTask + // game.net.recycler.maxCapacity.outboundBuffer + int maxCapacity = SystemPropertyUtil.getInt("game.net.recycler.maxCapacity.default", 0); + if (maxCapacity <= 0) { + // TODO: Some arbitrary large number - should adjust as we get more production experience. + maxCapacity = 262144; + } + + DEFAULT_MAX_CAPACITY = maxCapacity; + if (logger.isDebugEnabled()) { + logger.debug("-Dgame.net.recycler.maxCapacity.default: {}", DEFAULT_MAX_CAPACITY); + } + + INITIAL_CAPACITY = Math.min(DEFAULT_MAX_CAPACITY, 256); + } + + private final int maxCapacity; + private final FastThreadLocal> threadLocal = new FastThreadLocal>() { + @Override + protected Stack initialValue() { + return new Stack(Recycler.this, Thread.currentThread(), maxCapacity); + } + }; + + protected Recycler() { + this(DEFAULT_MAX_CAPACITY); + } + + protected Recycler(int maxCapacity) { + this.maxCapacity = Math.max(0, maxCapacity); + } + + + public final T get() { + Stack stack = threadLocal.get(); + DefaultHandle handle = stack.pop(); + if (handle == null) { + handle = stack.newHandle(); + handle.value = newObject(handle); + } + return (T) handle.value; + } + + public final boolean recycle(T o, Handle handle) { + DefaultHandle h = (DefaultHandle) handle; + if (h.stack.parent != this) { + return false; + } + if (o != h.value) { + throw new IllegalArgumentException("o does not belong to handle"); + } + h.recycle(); + return true; + } + + protected abstract T newObject(Handle handle); + + public interface Handle { } + + static final class DefaultHandle implements Handle { + private int lastRecycledId; + private int recycleId; + + private Stack stack; + private Object value; + + DefaultHandle(Stack stack) { + this.stack = stack; + } + + public void recycle() { + Thread thread = Thread.currentThread(); + if (thread == stack.thread) { + stack.push(this); + return; + } + // we don't want to have a ref to the queue as the value in our weak map + // so we null it out; to ensure there are no races with restoring it later + // we impose a memory ordering here (no-op on x86) + Map, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get(); + WeakOrderQueue queue = delayedRecycled.get(stack); + if (queue == null) { + delayedRecycled.put(stack, queue = new WeakOrderQueue(stack, thread)); + } + queue.add(this); + } + } + + private static final FastThreadLocal, WeakOrderQueue>> DELAYED_RECYCLED = + new FastThreadLocal, WeakOrderQueue>>() { + @Override + protected Map, WeakOrderQueue> initialValue() { + return new WeakHashMap, WeakOrderQueue>(); + } + }; + + // a queue that makes only moderate guarantees about visibility: items are seen in the correct order, + // but we aren't absolutely guaranteed to ever see anything at all, thereby keeping the queue cheap to maintain + private static final class WeakOrderQueue { + private static final int LINK_CAPACITY = 16; + + // Let Link extend AtomicInteger for intrinsics. The Link itself will be used as writerIndex. + + private static final class Link extends AtomicInteger { + private final DefaultHandle[] elements = new DefaultHandle[LINK_CAPACITY]; + + private int readIndex; + private Link next; + } + + // chain of data items + private Link head, tail; + // pointer to another queue of delayed items for the same stack + private WeakOrderQueue next; + private final WeakReference owner; + private final int id = ID_GENERATOR.getAndIncrement(); + + WeakOrderQueue(Stack stack, Thread thread) { + head = tail = new Link(); + owner = new WeakReference(thread); + synchronized (stack) { + next = stack.head; + stack.head = this; + } + } + + void add(DefaultHandle handle) { + handle.lastRecycledId = id; + + Link tail = this.tail; + int writeIndex; + if ((writeIndex = tail.get()) == LINK_CAPACITY) { + this.tail = tail = tail.next = new Link(); + writeIndex = tail.get(); + } + tail.elements[writeIndex] = handle; + handle.stack = null; + // we lazy set to ensure that setting stack to null appears before we unnull it in the owning thread; + // this also means we guarantee visibility of an element in the queue if we see the index updated + tail.lazySet(writeIndex + 1); + } + + boolean hasFinalData() { + return tail.readIndex != tail.get(); + } + + // transfer as many items as we can from this queue to the stack, returning true if any were transferred + + boolean transfer(Stack to) { + + Link head = this.head; + if (head == null) { + return false; + } + + if (head.readIndex == LINK_CAPACITY) { + if (head.next == null) { + return false; + } + this.head = head = head.next; + } + + int start = head.readIndex; + int end = head.get(); + if (start == end) { + return false; + } + + int count = end - start; + if (to.size + count > to.elements.length) { + to.elements = Arrays.copyOf(to.elements, (to.size + count) * 2); + } + + DefaultHandle[] src = head.elements; + DefaultHandle[] trg = to.elements; + int size = to.size; + while (start < end) { + DefaultHandle element = src[start]; + if (element.recycleId == 0) { + element.recycleId = element.lastRecycledId; + } else if (element.recycleId != element.lastRecycledId) { + throw new IllegalStateException("recycled already"); + } + element.stack = to; + trg[size++] = element; + src[start++] = null; + } + to.size = size; + + if (end == LINK_CAPACITY && head.next != null) { + this.head = head.next; + } + + head.readIndex = end; + return true; + } + } + + static final class Stack { + + // we keep a queue of per-thread queues, which is appended to once only, each time a new thread other + // than the stack owner recycles: when we run out of items in our stack we iterate this collection + // to scavenge those that can be reused. this permits us to incur minimal thread synchronisation whilst + // still recycling all items. + final Recycler parent; + final Thread thread; + private DefaultHandle[] elements; + private final int maxCapacity; + private int size; + + private volatile WeakOrderQueue head; + private WeakOrderQueue cursor, prev; + + Stack(Recycler parent, Thread thread, int maxCapacity) { + this.parent = parent; + this.thread = thread; + this.maxCapacity = maxCapacity; + elements = new DefaultHandle[INITIAL_CAPACITY]; + } + + DefaultHandle pop() { + int size = this.size; + if (size == 0) { + if (!scavenge()) { + return null; + } + size = this.size; + } + size --; + DefaultHandle ret = elements[size]; + if (ret.lastRecycledId != ret.recycleId) { + throw new IllegalStateException("recycled multiple times"); + } + ret.recycleId = 0; + ret.lastRecycledId = 0; + this.size = size; + return ret; + } + + boolean scavenge() { + // continue an existing scavenge, if any + if (scavengeSome()) { + return true; + } + + // reset our scavenge cursor + prev = null; + cursor = head; + return false; + } + + boolean scavengeSome() { + boolean success = false; + WeakOrderQueue cursor = this.cursor, prev = this.prev; + while (cursor != null) { + if (cursor.transfer(this)) { + success = true; + break; + } + WeakOrderQueue next = cursor.next; + if (cursor.owner.get() == null) { + // if the thread associated with the queue is gone, unlink it, after + // performing a volatile read to confirm there is no data left to collect + // we never unlink the first queue, as we don't want to synchronize on updating the head + if (cursor.hasFinalData()) { + for (;;) { + if (!cursor.transfer(this)) { + break; + } + } + } + if (prev != null) { + prev.next = next; + } + } else { + prev = cursor; + } + cursor = next; + } + this.prev = prev; + this.cursor = cursor; + return success; + } + + void push(DefaultHandle item) { + if ((item.recycleId | item.lastRecycledId) != 0) { + throw new IllegalStateException("recycled already"); + } + item.recycleId = item.lastRecycledId = OWN_THREAD_ID; + + int size = this.size; + if (size == elements.length) { + if (size == maxCapacity) { + // Hit the maximum capacity - drop the possibly youngest object. + return; + } + elements = Arrays.copyOf(elements, size << 1); + } + + elements[size] = item; + this.size = size + 1; + } + + DefaultHandle newHandle() { + return new DefaultHandle(this); + } + } +} diff --git a/common/src/common/net/util/ReferenceCountUtil.java b/common/src/common/net/util/ReferenceCountUtil.java new file mode 100644 index 0000000..7c55c48 --- /dev/null +++ b/common/src/common/net/util/ReferenceCountUtil.java @@ -0,0 +1,161 @@ +/* + * Copyright 2013 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 common.net.util; + +import common.net.util.internal.StringUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Collection of method to handle objects that may implement {@link ReferenceCounted}. + */ +public final class ReferenceCountUtil { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountUtil.class); + + /** + * Try to call {@link ReferenceCounted#retain()} if the specified message implements {@link ReferenceCounted}. + * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing. + */ + + public static T retain(T msg) { + if (msg instanceof ReferenceCounted) { + return (T) ((ReferenceCounted) msg).retain(); + } + return msg; + } + + /** + * Try to call {@link ReferenceCounted#retain()} if the specified message implements {@link ReferenceCounted}. + * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing. + */ + + public static T retain(T msg, int increment) { + if (msg instanceof ReferenceCounted) { + return (T) ((ReferenceCounted) msg).retain(increment); + } + return msg; + } + + /** + * Try to call {@link ReferenceCounted#release()} if the specified message implements {@link ReferenceCounted}. + * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing. + */ + public static boolean release(Object msg) { + if (msg instanceof ReferenceCounted) { + return ((ReferenceCounted) msg).release(); + } + return false; + } + + /** + * Try to call {@link ReferenceCounted#release(int)} if the specified message implements {@link ReferenceCounted}. + * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing. + */ + public static boolean release(Object msg, int decrement) { + if (msg instanceof ReferenceCounted) { + return ((ReferenceCounted) msg).release(decrement); + } + return false; + } + + /** + * Try to call {@link ReferenceCounted#release()} if the specified message implements {@link ReferenceCounted}. + * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing. + * Unlike {@link #release(Object)} this method catches an exception raised by {@link ReferenceCounted#release()} + * and logs it, rather than rethrowing it to the caller. It is usually recommended to use {@link #release(Object)} + * instead, unless you absolutely need to swallow an exception. + */ + public static void safeRelease(Object msg) { + try { + release(msg); + } catch (Throwable t) { + logger.warn("Failed to release a message: {}", msg, t); + } + } + + /** + * Try to call {@link ReferenceCounted#release(int)} if the specified message implements {@link ReferenceCounted}. + * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing. + * Unlike {@link #release(Object)} this method catches an exception raised by {@link ReferenceCounted#release(int)} + * and logs it, rather than rethrowing it to the caller. It is usually recommended to use + * {@link #release(Object, int)} instead, unless you absolutely need to swallow an exception. + */ + public static void safeRelease(Object msg, int decrement) { + try { + release(msg, decrement); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to release a message: {} (decrement: {})", msg, decrement, t); + } + } + } + + /** + * Schedules the specified object to be released when the caller thread terminates. Note that this operation is + * intended to simplify reference counting of ephemeral objects during unit tests. Do not use it beyond the + * intended use case. + */ + public static T releaseLater(T msg) { + return releaseLater(msg, 1); + } + + /** + * Schedules the specified object to be released when the caller thread terminates. Note that this operation is + * intended to simplify reference counting of ephemeral objects during unit tests. Do not use it beyond the + * intended use case. + */ + public static T releaseLater(T msg, int decrement) { + if (msg instanceof ReferenceCounted) { + ThreadDeathWatcher.watch(Thread.currentThread(), new ReleasingTask((ReferenceCounted) msg, decrement)); + } + return msg; + } + + /** + * Releases the objects when the thread that called {@link #releaseLater(Object)} has been terminated. + */ + private static final class ReleasingTask implements Runnable { + + private final ReferenceCounted obj; + private final int decrement; + + ReleasingTask(ReferenceCounted obj, int decrement) { + this.obj = obj; + this.decrement = decrement; + } + + @Override + public void run() { + try { + if (!obj.release(decrement)) { + logger.warn("Non-zero refCnt: {}", this); + } else { + logger.debug("Released: {}", this); + } + } catch (Exception ex) { + logger.warn("Failed to release an object: {}", obj, ex); + } + } + + @Override + public String toString() { + return StringUtil.simpleClassName(obj) + ".release(" + decrement + ") refCnt: " + obj.refCnt(); + } + } + + private ReferenceCountUtil() { } +} diff --git a/common/src/common/net/util/ReferenceCounted.java b/common/src/common/net/util/ReferenceCounted.java new file mode 100644 index 0000000..33d6097 --- /dev/null +++ b/common/src/common/net/util/ReferenceCounted.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 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 common.net.util; + +/** + * A reference-counted object that requires explicit deallocation. + *

+ * When a new {@link ReferenceCounted} is instantiated, it starts with the reference count of {@code 1}. + * {@link #retain()} increases the reference count, and {@link #release()} decreases the reference count. + * If the reference count is decreased to {@code 0}, the object will be deallocated explicitly, and accessing + * the deallocated object will usually result in an access violation. + *

+ *

+ * If an object that implements {@link ReferenceCounted} is a container of other objects that implement + * {@link ReferenceCounted}, the contained objects will also be released via {@link #release()} when the container's + * reference count becomes 0. + *

+ */ +public interface ReferenceCounted { + /** + * Returns the reference count of this object. If {@code 0}, it means this object has been deallocated. + */ + int refCnt(); + + /** + * Increases the reference count by {@code 1}. + */ + ReferenceCounted retain(); + + /** + * Increases the reference count by the specified {@code increment}. + */ + ReferenceCounted retain(int increment); + + /** + * Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at + * {@code 0}. + * + * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated + */ + boolean release(); + + /** + * Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference + * count reaches at {@code 0}. + * + * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated + */ + boolean release(int decrement); +} diff --git a/common/src/common/net/util/ResourceLeak.java b/common/src/common/net/util/ResourceLeak.java new file mode 100644 index 0000000..00064a7 --- /dev/null +++ b/common/src/common/net/util/ResourceLeak.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 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 common.net.util; + +public interface ResourceLeak { + /** + * Records the caller's current stack trace so that the {@link ResourceLeakDetector} can tell where the leaked + * resource was accessed lastly. + */ + void record(); + + /** + * Close the leak so that {@link ResourceLeakDetector} does not warn about leaked resources. + * + * @return {@code true} if called first time, {@code false} if called already + */ + boolean close(); +} diff --git a/common/src/common/net/util/ResourceLeakDetector.java b/common/src/common/net/util/ResourceLeakDetector.java new file mode 100644 index 0000000..e5e4e96 --- /dev/null +++ b/common/src/common/net/util/ResourceLeakDetector.java @@ -0,0 +1,388 @@ +/* + * Copyright 2013 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 common.net.util; + +import static common.net.util.internal.StringUtil.NEWLINE; +import static common.net.util.internal.StringUtil.simpleClassName; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.EnumSet; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.SystemPropertyUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +public final class ResourceLeakDetector { + + private static final String PROP_LEVEL = "game.net.leakDetectionLevel"; + private static final Level DEFAULT_LEVEL = Level.SIMPLE; + + /** + * Represents the level of resource leak detection. + */ + public enum Level { + /** + * Disables resource leak detection. + */ + DISABLED, + /** + * Enables simplistic sampling resource leak detection which reports there is a leak or not, + * at the cost of small overhead (default). + */ + SIMPLE, + /** + * Enables advanced sampling resource leak detection which reports where the leaked object was accessed + * recently at the cost of high overhead. + */ + ADVANCED, + /** + * Enables paranoid resource leak detection which reports where the leaked object was accessed recently, + * at the cost of the highest possible overhead (for testing purposes only). + */ + PARANOID + } + + private static Level level; + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ResourceLeakDetector.class); + + static { + final boolean disabled; + if (SystemPropertyUtil.get("game.net.noResourceLeakDetection") != null) { + disabled = SystemPropertyUtil.getBoolean("game.net.noResourceLeakDetection", false); + logger.debug("-Dgame.net.noResourceLeakDetection: {}", disabled); + logger.warn( + "-Dgame.net.noResourceLeakDetection is deprecated. Use '-D{}={}' instead.", + PROP_LEVEL, DEFAULT_LEVEL.name().toLowerCase()); + } else { + disabled = false; + } + + Level defaultLevel = disabled? Level.DISABLED : DEFAULT_LEVEL; + String levelStr = SystemPropertyUtil.get(PROP_LEVEL, defaultLevel.name()).trim().toUpperCase(); + Level level = DEFAULT_LEVEL; + for (Level l: EnumSet.allOf(Level.class)) { + if (levelStr.equals(l.name()) || levelStr.equals(String.valueOf(l.ordinal()))) { + level = l; + } + } + + ResourceLeakDetector.level = level; + if (logger.isDebugEnabled()) { + logger.debug("-D{}: {}", PROP_LEVEL, level.name().toLowerCase()); + } + } + + private static final int DEFAULT_SAMPLING_INTERVAL = 113; + + /** + * @deprecated Use {@link #setLevel(Level)} instead. + */ + @Deprecated + public static void setEnabled(boolean enabled) { + setLevel(enabled? Level.SIMPLE : Level.DISABLED); + } + + /** + * Returns {@code true} if resource leak detection is enabled. + */ + public static boolean isEnabled() { + return getLevel().ordinal() > Level.DISABLED.ordinal(); + } + + /** + * Sets the resource leak detection level. + */ + public static void setLevel(Level level) { + if (level == null) { + throw new NullPointerException("level"); + } + ResourceLeakDetector.level = level; + } + + /** + * Returns the current resource leak detection level. + */ + public static Level getLevel() { + return level; + } + + /** the linked list of active resources */ + private final DefaultResourceLeak head = new DefaultResourceLeak(null); + private final DefaultResourceLeak tail = new DefaultResourceLeak(null); + + private final ReferenceQueue refQueue = new ReferenceQueue(); + private final ConcurrentMap reportedLeaks = PlatformDependent.newConcurrentHashMap(); + + private final String resourceType; + private final int samplingInterval; + private final long maxActive; + private long active; + private final AtomicBoolean loggedTooManyActive = new AtomicBoolean(); + + private long leakCheckCnt; + + public ResourceLeakDetector(Class resourceType) { + this(simpleClassName(resourceType)); + } + + public ResourceLeakDetector(String resourceType) { + this(resourceType, DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE); + } + + public ResourceLeakDetector(Class resourceType, int samplingInterval, long maxActive) { + this(simpleClassName(resourceType), samplingInterval, maxActive); + } + + public ResourceLeakDetector(String resourceType, int samplingInterval, long maxActive) { + if (resourceType == null) { + throw new NullPointerException("resourceType"); + } + if (samplingInterval <= 0) { + throw new IllegalArgumentException("samplingInterval: " + samplingInterval + " (expected: 1+)"); + } + if (maxActive <= 0) { + throw new IllegalArgumentException("maxActive: " + maxActive + " (expected: 1+)"); + } + + this.resourceType = resourceType; + this.samplingInterval = samplingInterval; + this.maxActive = maxActive; + + head.next = tail; + tail.prev = head; + } + + /** + * Creates a new {@link ResourceLeak} which is expected to be closed via {@link ResourceLeak#close()} when the + * related resource is deallocated. + * + * @return the {@link ResourceLeak} or {@code null} + */ + public ResourceLeak open(T obj) { + Level level = ResourceLeakDetector.level; + if (level == Level.DISABLED) { + return null; + } + + if (level.ordinal() < Level.PARANOID.ordinal()) { + if (leakCheckCnt ++ % samplingInterval == 0) { + reportLeak(level); + return new DefaultResourceLeak(obj); + } else { + return null; + } + } else { + reportLeak(level); + return new DefaultResourceLeak(obj); + } + } + + private void reportLeak(Level level) { + if (!logger.isErrorEnabled()) { + for (;;) { + + DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll(); + if (ref == null) { + break; + } + ref.close(); + } + return; + } + + // Report too many instances. + int samplingInterval = level == Level.PARANOID? 1 : this.samplingInterval; + if (active * samplingInterval > maxActive && loggedTooManyActive.compareAndSet(false, true)) { + logger.error("LEAK: You are creating too many " + resourceType + " instances. " + + resourceType + " is a shared resource that must be reused across the JVM," + + "so that only a few instances are created."); + } + + // Detect and report previous leaks. + for (;;) { + + DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll(); + if (ref == null) { + break; + } + + ref.clear(); + + if (!ref.close()) { + continue; + } + + String records = ref.toString(); + if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) { + if (records.isEmpty()) { + logger.error("LEAK: {}.release() was not called before it's garbage-collected. " + + "Enable advanced leak reporting to find out where the leak occurred. " + + "To enable advanced leak reporting, " + + "specify the JVM option '-D{}={}' or call {}.setLevel()", + resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this)); + } else { + logger.error( + "LEAK: {}.release() was not called before it's garbage-collected.{}", + resourceType, records); + } + } + } + } + + private final class DefaultResourceLeak extends PhantomReference implements ResourceLeak { + + private static final int MAX_RECORDS = 4; + + private final String creationRecord; + private final Deque lastRecords = new ArrayDeque(); + private final AtomicBoolean freed; + private DefaultResourceLeak prev; + private DefaultResourceLeak next; + + DefaultResourceLeak(Object referent) { + super(referent, referent != null? refQueue : null); + + if (referent != null) { + Level level = getLevel(); + if (level.ordinal() >= Level.ADVANCED.ordinal()) { + creationRecord = newRecord(3); + } else { + creationRecord = null; + } + + // TODO: Use CAS to update the list. + synchronized (head) { + prev = head; + next = head.next; + head.next.prev = this; + head.next = this; + active ++; + } + freed = new AtomicBoolean(); + } else { + creationRecord = null; + freed = new AtomicBoolean(true); + } + } + + @Override + public void record() { + if (creationRecord != null) { + String value = newRecord(2); + + synchronized (lastRecords) { + int size = lastRecords.size(); + if (size == 0 || !lastRecords.getLast().equals(value)) { + lastRecords.add(value); + } + if (size > MAX_RECORDS) { + lastRecords.removeFirst(); + } + } + } + } + + @Override + public boolean close() { + if (freed.compareAndSet(false, true)) { + synchronized (head) { + active --; + prev.next = next; + next.prev = prev; + prev = null; + next = null; + } + return true; + } + return false; + } + + public String toString() { + if (creationRecord == null) { + return ""; + } + + Object[] array; + synchronized (lastRecords) { + array = lastRecords.toArray(); + } + + StringBuilder buf = new StringBuilder(16384); + buf.append(NEWLINE); + buf.append("Recent access records: "); + buf.append(array.length); + buf.append(NEWLINE); + + if (array.length > 0) { + for (int i = array.length - 1; i >= 0; i --) { + buf.append('#'); + buf.append(i + 1); + buf.append(':'); + buf.append(NEWLINE); + buf.append(array[i]); + } + } + + buf.append("Created at:"); + buf.append(NEWLINE); + buf.append(creationRecord); + buf.setLength(buf.length() - NEWLINE.length()); + + return buf.toString(); + } + } + + private static final String[] STACK_TRACE_ELEMENT_EXCLUSIONS = { + "game.net.buffer.AbstractByteBufAllocator.toLeakAwareBuffer(", + }; + + static String newRecord(int recordsToSkip) { + StringBuilder buf = new StringBuilder(4096); + StackTraceElement[] array = new Throwable().getStackTrace(); + for (StackTraceElement e: array) { + if (recordsToSkip > 0) { + recordsToSkip --; + } else { + String estr = e.toString(); + + // Strip the noisy stack trace elements. + boolean excluded = false; + for (String exclusion: STACK_TRACE_ELEMENT_EXCLUSIONS) { + if (estr.startsWith(exclusion)) { + excluded = true; + break; + } + } + + if (!excluded) { + buf.append('\t'); + buf.append(estr); + buf.append(NEWLINE); + } + } + } + + return buf.toString(); + } +} diff --git a/common/src/common/net/util/Signal.java b/common/src/common/net/util/Signal.java new file mode 100644 index 0000000..eec3fd1 --- /dev/null +++ b/common/src/common/net/util/Signal.java @@ -0,0 +1,77 @@ +/* + * 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 common.net.util; + + +import java.util.concurrent.ConcurrentMap; + +import common.net.util.internal.PlatformDependent; + +/** + * A special {@link Error} which is used to signal some state or request by throwing it. + * {@link Signal} has an empty stack trace and has no cause to save the instantiation overhead. + */ +public final class Signal extends Error { + + private static final long serialVersionUID = -221145131122459977L; + + private static final ConcurrentMap map = PlatformDependent.newConcurrentHashMap(); + + + private final UniqueName uname; + + /** + * Creates a new {@link Signal} with the specified {@code name}. + */ + + public static Signal valueOf(String name) { + return new Signal(name); + } + + /** + * @deprecated Use {@link #valueOf(String)} instead. + */ + @Deprecated + public Signal(String name) { + super(name); + uname = new UniqueName(map, name); + } + + /** + * Check if the given {@link Signal} is the same as this instance. If not an {@link IllegalStateException} will + * be thrown. + */ + public void expect(Signal signal) { + if (this != signal) { + throw new IllegalStateException("unexpected signal: " + signal); + } + } + + @Override + public Throwable initCause(Throwable cause) { + return this; + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + @Override + public String toString() { + return uname.name(); + } +} diff --git a/common/src/common/net/util/ThreadDeathWatcher.java b/common/src/common/net/util/ThreadDeathWatcher.java new file mode 100644 index 0000000..af972be --- /dev/null +++ b/common/src/common/net/util/ThreadDeathWatcher.java @@ -0,0 +1,241 @@ +/* + * Copyright 2014 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 common.net.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import common.net.util.concurrent.DefaultThreadFactory; +import common.net.util.internal.MpscLinkedQueueNode; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Checks if a thread is alive periodically and runs a task when a thread dies. + *

+ * This thread starts a daemon thread to check the state of the threads being watched and to invoke their + * associated {@link Runnable}s. When there is no thread to watch (i.e. all threads are dead), the daemon thread + * will terminate itself, and a new daemon thread will be started again when a new watch is added. + *

+ */ +public final class ThreadDeathWatcher { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ThreadDeathWatcher.class); + private static final ThreadFactory threadFactory = + new DefaultThreadFactory(ThreadDeathWatcher.class, true, Thread.MIN_PRIORITY); + + private static final Queue pendingEntries = PlatformDependent.newMpscQueue(); + private static final Watcher watcher = new Watcher(); + private static final AtomicBoolean started = new AtomicBoolean(); + private static volatile Thread watcherThread; + + /** + * Schedules the specified {@code task} to run when the specified {@code thread} dies. + * + * @param thread the {@link Thread} to watch + * @param task the {@link Runnable} to run when the {@code thread} dies + * + * @throws IllegalArgumentException if the specified {@code thread} is not alive + */ + public static void watch(Thread thread, Runnable task) { + if (thread == null) { + throw new NullPointerException("thread"); + } + if (task == null) { + throw new NullPointerException("task"); + } + if (!thread.isAlive()) { + throw new IllegalArgumentException("thread must be alive."); + } + + schedule(thread, task, true); + } + + /** + * Cancels the task scheduled via {@link #watch(Thread, Runnable)}. + */ + public static void unwatch(Thread thread, Runnable task) { + if (thread == null) { + throw new NullPointerException("thread"); + } + if (task == null) { + throw new NullPointerException("task"); + } + + schedule(thread, task, false); + } + + private static void schedule(Thread thread, Runnable task, boolean isWatch) { + pendingEntries.add(new Entry(thread, task, isWatch)); + + if (started.compareAndSet(false, true)) { + Thread watcherThread = threadFactory.newThread(watcher); + watcherThread.start(); + ThreadDeathWatcher.watcherThread = watcherThread; + } + } + + /** + * Waits until the thread of this watcher has no threads to watch and terminates itself. + * Because a new watcher thread will be started again on {@link #watch(Thread, Runnable)}, + * this operation is only useful when you want to ensure that the watcher thread is terminated + * after your application is shut down and there's no chance of calling + * {@link #watch(Thread, Runnable)} afterwards. + * + * @return {@code true} if and only if the watcher thread has been terminated + */ + public static boolean awaitInactivity(long timeout, TimeUnit unit) throws InterruptedException { + if (unit == null) { + throw new NullPointerException("unit"); + } + + Thread watcherThread = ThreadDeathWatcher.watcherThread; + if (watcherThread != null) { + watcherThread.join(unit.toMillis(timeout)); + return !watcherThread.isAlive(); + } else { + return true; + } + } + + private ThreadDeathWatcher() { } + + private static final class Watcher implements Runnable { + + private final List watchees = new ArrayList(); + + @Override + public void run() { + for (;;) { + fetchWatchees(); + notifyWatchees(); + + // Try once again just in case notifyWatchees() triggered watch() or unwatch(). + fetchWatchees(); + notifyWatchees(); + + try { + Thread.sleep(1000); + } catch (InterruptedException ignore) { + // Ignore the interrupt; do not terminate until all tasks are run. + } + + if (watchees.isEmpty() && pendingEntries.isEmpty()) { + + // Mark the current worker thread as stopped. + // The following CAS must always success and must be uncontended, + // because only one watcher thread should be running at the same time. + boolean stopped = started.compareAndSet(true, false); + assert stopped; + + // Check if there are pending entries added by watch() while we do CAS above. + if (pendingEntries.isEmpty()) { + // A) watch() was not invoked and thus there's nothing to handle + // -> safe to terminate because there's nothing left to do + // B) a new watcher thread started and handled them all + // -> safe to terminate the new watcher thread will take care the rest + break; + } + + // There are pending entries again, added by watch() + if (!started.compareAndSet(false, true)) { + // watch() started a new watcher thread and set 'started' to true. + // -> terminate this thread so that the new watcher reads from pendingEntries exclusively. + break; + } + + // watch() added an entry, but this worker was faster to set 'started' to true. + // i.e. a new watcher thread was not started + // -> keep this thread alive to handle the newly added entries. + } + } + } + + private void fetchWatchees() { + for (;;) { + Entry e = pendingEntries.poll(); + if (e == null) { + break; + } + + if (e.isWatch) { + watchees.add(e); + } else { + watchees.remove(e); + } + } + } + + private void notifyWatchees() { + List watchees = this.watchees; + for (int i = 0; i < watchees.size();) { + Entry e = watchees.get(i); + if (!e.thread.isAlive()) { + watchees.remove(i); + try { + e.task.run(); + } catch (Throwable t) { + logger.warn("Thread death watcher task raised an exception:", t); + } + } else { + i ++; + } + } + } + } + + private static final class Entry extends MpscLinkedQueueNode { + final Thread thread; + final Runnable task; + final boolean isWatch; + + Entry(Thread thread, Runnable task, boolean isWatch) { + this.thread = thread; + this.task = task; + this.isWatch = isWatch; + } + + @Override + public Entry value() { + return this; + } + + @Override + public int hashCode() { + return thread.hashCode() ^ task.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Entry)) { + return false; + } + + Entry that = (Entry) obj; + return thread == that.thread && task == that.task; + } + } +} diff --git a/common/src/common/net/util/UniqueName.java b/common/src/common/net/util/UniqueName.java new file mode 100644 index 0000000..706670e --- /dev/null +++ b/common/src/common/net/util/UniqueName.java @@ -0,0 +1,117 @@ +/* + * 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 common.net.util; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @deprecated Known to have problems with class loaders. + * + * Defines a name that must be unique in the map that is provided during construction. + */ +@Deprecated +public class UniqueName implements Comparable { + + private static final AtomicInteger nextId = new AtomicInteger(); + + private final int id; + private final String name; + + /** + * Constructs a new {@link UniqueName} + * + * @param map the map of names to compare with + * @param name the name of this {@link UniqueName} + * @param args the arguments to process + */ + public UniqueName(ConcurrentMap map, String name, Object... args) { + if (map == null) { + throw new NullPointerException("map"); + } + if (name == null) { + throw new NullPointerException("name"); + } + if (args != null && args.length > 0) { + validateArgs(args); + } + + if (map.putIfAbsent(name, Boolean.TRUE) != null) { + throw new IllegalArgumentException(String.format("'%s' is already in use", name)); + } + + id = nextId.incrementAndGet(); + this.name = name; + } + + /** + * Validates the given arguments. This method does not do anything on its own, but must be + * overridden by its subclasses. + * + * @param args arguments to validate + */ + + protected void validateArgs(Object... args) { + // Subclasses will override. + } + + /** + * Returns this {@link UniqueName}'s name + * + * @return the name + */ + public final String name() { + return name; + } + + /** + * Returns this {@link UniqueName}'s ID + * + * @return the id + */ + public final int id() { + return id; + } + + @Override + public final int hashCode() { + return super.hashCode(); + } + + @Override + public final boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int compareTo(UniqueName other) { + if (this == other) { + return 0; + } + + int returnCode = name.compareTo(other.name); + if (returnCode != 0) { + return returnCode; + } + + return ((Integer) id).compareTo(other.id); + } + + @Override + public String toString() { + return name(); + } +} diff --git a/common/src/common/net/util/concurrent/AbstractEventExecutor.java b/common/src/common/net/util/concurrent/AbstractEventExecutor.java new file mode 100644 index 0000000..1392ed6 --- /dev/null +++ b/common/src/common/net/util/concurrent/AbstractEventExecutor.java @@ -0,0 +1,157 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Abstract base class for {@link EventExecutor} implementations. + */ +public abstract class AbstractEventExecutor extends AbstractExecutorService implements EventExecutor { + + @Override + public EventExecutor next() { + return this; + } + + @Override + public boolean inEventLoop() { + return inEventLoop(Thread.currentThread()); + } + + @Override + public Iterator iterator() { + return new EventExecutorIterator(); + } + + @Override + public Future shutdownGracefully() { + return shutdownGracefully(2, 15, TimeUnit.SECONDS); + } + + /** + * @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead. + */ + @Override + @Deprecated + public abstract void shutdown(); + + /** + * @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead. + */ + @Override + @Deprecated + public List shutdownNow() { + shutdown(); + return Collections.emptyList(); + } + + @Override + public Promise newPromise() { + return new DefaultPromise(this); + } + + @Override + public ProgressivePromise newProgressivePromise() { + return new DefaultProgressivePromise(this); + } + + @Override + public Future newSucceededFuture(V result) { + return new SucceededFuture(this, result); + } + + @Override + public Future newFailedFuture(Throwable cause) { + return new FailedFuture(this, cause); + } + + @Override + public Future submit(Runnable task) { + return (Future) super.submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return (Future) super.submit(task, result); + } + + @Override + public Future submit(Callable task) { + return (Future) super.submit(task); + } + + @Override + protected final RunnableFuture newTaskFor(Runnable runnable, T value) { + return new PromiseTask(this, runnable, value); + } + + @Override + protected final RunnableFuture newTaskFor(Callable callable) { + return new PromiseTask(this, callable); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, + TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + private final class EventExecutorIterator implements Iterator { + private boolean nextCalled; + + @Override + public boolean hasNext() { + return !nextCalled; + } + + @Override + public EventExecutor next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + nextCalled = true; + return AbstractEventExecutor.this; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("read-only"); + } + } +} diff --git a/common/src/common/net/util/concurrent/AbstractEventExecutorGroup.java b/common/src/common/net/util/concurrent/AbstractEventExecutorGroup.java new file mode 100644 index 0000000..8c1b768 --- /dev/null +++ b/common/src/common/net/util/concurrent/AbstractEventExecutorGroup.java @@ -0,0 +1,116 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Abstract base class for {@link EventExecutorGroup} implementations. + */ +public abstract class AbstractEventExecutorGroup implements EventExecutorGroup { + + @Override + public Future submit(Runnable task) { + return next().submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return next().submit(task, result); + } + + @Override + public Future submit(Callable task) { + return next().submit(task); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return next().schedule(command, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return next().schedule(callable, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return next().scheduleAtFixedRate(command, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return next().scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + @Override + public Future shutdownGracefully() { + return shutdownGracefully(2, 15, TimeUnit.SECONDS); + } + + /** + * @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead. + */ + @Override + @Deprecated + public abstract void shutdown(); + + /** + * @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead. + */ + @Override + @Deprecated + public List shutdownNow() { + shutdown(); + return Collections.emptyList(); + } + + @Override + public List> invokeAll(Collection> tasks) + throws InterruptedException { + return next().invokeAll(tasks); + } + + @Override + public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { + return next().invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return next().invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return next().invokeAny(tasks, timeout, unit); + } + + @Override + public void execute(Runnable command) { + next().execute(command); + } +} diff --git a/common/src/common/net/util/concurrent/AbstractFuture.java b/common/src/common/net/util/concurrent/AbstractFuture.java new file mode 100644 index 0000000..e288643 --- /dev/null +++ b/common/src/common/net/util/concurrent/AbstractFuture.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Abstract {@link Future} implementation which does not allow for cancellation. + * + * @param + */ +public abstract class AbstractFuture implements Future { + + @Override + public V get() throws InterruptedException, ExecutionException { + await(); + + Throwable cause = cause(); + if (cause == null) { + return getNow(); + } + throw new ExecutionException(cause); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + if (await(timeout, unit)) { + Throwable cause = cause(); + if (cause == null) { + return getNow(); + } + throw new ExecutionException(cause); + } + throw new TimeoutException(); + } +} diff --git a/common/src/common/net/util/concurrent/BlockingOperationException.java b/common/src/common/net/util/concurrent/BlockingOperationException.java new file mode 100644 index 0000000..725b524 --- /dev/null +++ b/common/src/common/net/util/concurrent/BlockingOperationException.java @@ -0,0 +1,41 @@ +/* + * 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 common.net.util.concurrent; + +/** + * An {@link IllegalStateException} which is raised when a user performed a blocking operation + * when the user is in an event loop thread. If a blocking operation is performed in an event loop + * thread, the blocking operation will most likely enter a dead lock state, hence throwing this + * exception. + */ +public class BlockingOperationException extends IllegalStateException { + + private static final long serialVersionUID = 2462223247762460301L; + + public BlockingOperationException() { } + + public BlockingOperationException(String s) { + super(s); + } + + public BlockingOperationException(Throwable cause) { + super(cause); + } + + public BlockingOperationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/common/net/util/concurrent/CompleteFuture.java b/common/src/common/net/util/concurrent/CompleteFuture.java new file mode 100644 index 0000000..2761a6e --- /dev/null +++ b/common/src/common/net/util/concurrent/CompleteFuture.java @@ -0,0 +1,147 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.concurrent.TimeUnit; + +/** + * A skeletal {@link Future} implementation which represents a {@link Future} which has been completed already. + */ +public abstract class CompleteFuture extends AbstractFuture { + + private final EventExecutor executor; + + /** + * Creates a new instance. + * + * @param executor the {@link EventExecutor} associated with this future + */ + protected CompleteFuture(EventExecutor executor) { + this.executor = executor; + } + + /** + * Return the {@link EventExecutor} which is used by this {@link CompleteFuture}. + */ + protected EventExecutor executor() { + return executor; + } + + @Override + public Future addListener(GenericFutureListener> listener) { + if (listener == null) { + throw new NullPointerException("listener"); + } + DefaultPromise.notifyListener(executor(), this, listener); + return this; + } + + @Override + public Future addListeners(GenericFutureListener>... listeners) { + if (listeners == null) { + throw new NullPointerException("listeners"); + } + for (GenericFutureListener> l: listeners) { + if (l == null) { + break; + } + DefaultPromise.notifyListener(executor(), this, l); + } + return this; + } + + @Override + public Future removeListener(GenericFutureListener> listener) { + // NOOP + return this; + } + + @Override + public Future removeListeners(GenericFutureListener>... listeners) { + // NOOP + return this; + } + + @Override + public Future await() throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + return this; + } + + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + return true; + } + + @Override + public Future sync() throws InterruptedException { + return this; + } + + @Override + public Future syncUninterruptibly() { + return this; + } + + @Override + public boolean await(long timeoutMillis) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + return true; + } + + @Override + public Future awaitUninterruptibly() { + return this; + } + + @Override + public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { + return true; + } + + @Override + public boolean awaitUninterruptibly(long timeoutMillis) { + return true; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public boolean isCancellable() { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } +} diff --git a/common/src/common/net/util/concurrent/DefaultEventExecutor.java b/common/src/common/net/util/concurrent/DefaultEventExecutor.java new file mode 100644 index 0000000..7f7c611 --- /dev/null +++ b/common/src/common/net/util/concurrent/DefaultEventExecutor.java @@ -0,0 +1,45 @@ +/* + * 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 common.net.util.concurrent; + +import java.util.concurrent.ThreadFactory; + +/** + * Default {@link SingleThreadEventExecutor} implementation which just execute all submitted task in a + * serial fashion + * + */ +final class DefaultEventExecutor extends SingleThreadEventExecutor { + + DefaultEventExecutor(DefaultEventExecutorGroup parent, ThreadFactory threadFactory) { + super(parent, threadFactory, true); + } + + @Override + protected void run() { + for (;;) { + Runnable task = takeTask(); + if (task != null) { + task.run(); + updateLastExecutionTime(); + } + + if (confirmShutdown()) { + break; + } + } + } +} diff --git a/common/src/common/net/util/concurrent/DefaultEventExecutorGroup.java b/common/src/common/net/util/concurrent/DefaultEventExecutorGroup.java new file mode 100644 index 0000000..10c165a --- /dev/null +++ b/common/src/common/net/util/concurrent/DefaultEventExecutorGroup.java @@ -0,0 +1,48 @@ +/* + * 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 common.net.util.concurrent; + +import java.util.concurrent.ThreadFactory; + +/** + * Default implementation of {@link MultithreadEventExecutorGroup} which will use {@link DefaultEventExecutor} instances + * to handle the tasks. + */ +public class DefaultEventExecutorGroup extends MultithreadEventExecutorGroup { + + /** + * @see {@link #DefaultEventExecutorGroup(int, ThreadFactory)} + */ + public DefaultEventExecutorGroup(int nThreads) { + this(nThreads, null); + } + + /** + * Create a new instance. + * + * @param nThreads the number of threads that will be used by this instance. + * @param threadFactory the ThreadFactory to use, or {@code null} if the default should be used. + */ + public DefaultEventExecutorGroup(int nThreads, ThreadFactory threadFactory) { + super(nThreads, threadFactory); + } + + @Override + protected EventExecutor newChild( + ThreadFactory threadFactory, Object... args) throws Exception { + return new DefaultEventExecutor(this, threadFactory); + } +} diff --git a/common/src/common/net/util/concurrent/DefaultFutureListeners.java b/common/src/common/net/util/concurrent/DefaultFutureListeners.java new file mode 100644 index 0000000..37a830e --- /dev/null +++ b/common/src/common/net/util/concurrent/DefaultFutureListeners.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.Arrays; + +final class DefaultFutureListeners { + + private GenericFutureListener>[] listeners; + private int size; + private int progressiveSize; // the number of progressive listeners + + + DefaultFutureListeners( + GenericFutureListener> first, GenericFutureListener> second) { + listeners = new GenericFutureListener[2]; + listeners[0] = first; + listeners[1] = second; + size = 2; + if (first instanceof GenericProgressiveFutureListener) { + progressiveSize ++; + } + if (second instanceof GenericProgressiveFutureListener) { + progressiveSize ++; + } + } + + public void add(GenericFutureListener> l) { + GenericFutureListener>[] listeners = this.listeners; + final int size = this.size; + if (size == listeners.length) { + this.listeners = listeners = Arrays.copyOf(listeners, size << 1); + } + listeners[size] = l; + this.size = size + 1; + + if (l instanceof GenericProgressiveFutureListener) { + progressiveSize ++; + } + } + + public void remove(GenericFutureListener> l) { + final GenericFutureListener>[] listeners = this.listeners; + int size = this.size; + for (int i = 0; i < size; i ++) { + if (listeners[i] == l) { + int listenersToMove = size - i - 1; + if (listenersToMove > 0) { + System.arraycopy(listeners, i + 1, listeners, i, listenersToMove); + } + listeners[-- size] = null; + this.size = size; + + if (l instanceof GenericProgressiveFutureListener) { + progressiveSize --; + } + return; + } + } + } + + public GenericFutureListener>[] listeners() { + return listeners; + } + + public int size() { + return size; + } + + public int progressiveSize() { + return progressiveSize; + } +} diff --git a/common/src/common/net/util/concurrent/DefaultProgressivePromise.java b/common/src/common/net/util/concurrent/DefaultProgressivePromise.java new file mode 100644 index 0000000..57d908a --- /dev/null +++ b/common/src/common/net/util/concurrent/DefaultProgressivePromise.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +public class DefaultProgressivePromise extends DefaultPromise implements ProgressivePromise { + + /** + * Creates a new instance. + * + * It is preferable to use {@link EventExecutor#newProgressivePromise()} to create a new progressive promise + * + * @param executor + * the {@link EventExecutor} which is used to notify the promise when it progresses or it is complete + */ + public DefaultProgressivePromise(EventExecutor executor) { + super(executor); + } + + protected DefaultProgressivePromise() { /* only for subclasses */ } + + @Override + public ProgressivePromise setProgress(long progress, long total) { + if (total < 0) { + // total unknown + total = -1; // normalize + if (progress < 0) { + throw new IllegalArgumentException("progress: " + progress + " (expected: >= 0)"); + } + } else if (progress < 0 || progress > total) { + throw new IllegalArgumentException( + "progress: " + progress + " (expected: 0 <= progress <= total (" + total + "))"); + } + + if (isDone()) { + throw new IllegalStateException("complete already"); + } + + notifyProgressiveListeners(progress, total); + return this; + } + + @Override + public boolean tryProgress(long progress, long total) { + if (total < 0) { + total = -1; + if (progress < 0 || isDone()) { + return false; + } + } else if (progress < 0 || progress > total || isDone()) { + return false; + } + + notifyProgressiveListeners(progress, total); + return true; + } + + @Override + public ProgressivePromise addListener(GenericFutureListener> listener) { + super.addListener(listener); + return this; + } + + @Override + public ProgressivePromise addListeners(GenericFutureListener>... listeners) { + super.addListeners(listeners); + return this; + } + + @Override + public ProgressivePromise removeListener(GenericFutureListener> listener) { + super.removeListener(listener); + return this; + } + + @Override + public ProgressivePromise removeListeners(GenericFutureListener>... listeners) { + super.removeListeners(listeners); + return this; + } + + @Override + public ProgressivePromise sync() throws InterruptedException { + super.sync(); + return this; + } + + @Override + public ProgressivePromise syncUninterruptibly() { + super.syncUninterruptibly(); + return this; + } + + @Override + public ProgressivePromise await() throws InterruptedException { + super.await(); + return this; + } + + @Override + public ProgressivePromise awaitUninterruptibly() { + super.awaitUninterruptibly(); + return this; + } + + @Override + public ProgressivePromise setSuccess(V result) { + super.setSuccess(result); + return this; + } + + @Override + public ProgressivePromise setFailure(Throwable cause) { + super.setFailure(cause); + return this; + } +} diff --git a/common/src/common/net/util/concurrent/DefaultPromise.java b/common/src/common/net/util/concurrent/DefaultPromise.java new file mode 100644 index 0000000..eb38442 --- /dev/null +++ b/common/src/common/net/util/concurrent/DefaultPromise.java @@ -0,0 +1,876 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayDeque; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; + +import common.net.util.Signal; +import common.net.util.internal.EmptyArrays; +import common.net.util.internal.InternalThreadLocalMap; +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.StringUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +public class DefaultPromise extends AbstractFuture implements Promise { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultPromise.class); + private static final InternalLogger rejectedExecutionLogger = + InternalLoggerFactory.getInstance(DefaultPromise.class.getName() + ".rejectedExecution"); + + private static final int MAX_LISTENER_STACK_DEPTH = 8; + private static final Signal SUCCESS = Signal.valueOf(DefaultPromise.class.getName() + ".SUCCESS"); + private static final Signal UNCANCELLABLE = Signal.valueOf(DefaultPromise.class.getName() + ".UNCANCELLABLE"); + private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(new CancellationException()); + + static { + CANCELLATION_CAUSE_HOLDER.cause.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE); + } + + private final EventExecutor executor; + + private volatile Object result; + + /** + * One or more listeners. Can be a {@link GenericFutureListener} or a {@link DefaultFutureListeners}. + * If {@code null}, it means either 1) no listeners were added yet or 2) all listeners were notified. + */ + private Object listeners; + + /** + * The list of the listeners that were added after the promise is done. Initially {@code null} and lazily + * instantiated when the late listener is scheduled to be notified later. Also used as a cached {@link Runnable} + * that performs the notification of the listeners it contains. + */ + private LateListeners lateListeners; + + private short waiters; + + /** + * Creates a new instance. + * + * It is preferable to use {@link EventExecutor#newPromise()} to create a new promise + * + * @param executor + * the {@link EventExecutor} which is used to notify the promise once it is complete + */ + public DefaultPromise(EventExecutor executor) { + if (executor == null) { + throw new NullPointerException("executor"); + } + this.executor = executor; + } + + protected DefaultPromise() { + // only for subclasses + executor = null; + } + + protected EventExecutor executor() { + return executor; + } + + @Override + public boolean isCancelled() { + return isCancelled0(result); + } + + private static boolean isCancelled0(Object result) { + return result instanceof CauseHolder && ((CauseHolder) result).cause instanceof CancellationException; + } + + @Override + public boolean isCancellable() { + return result == null; + } + + @Override + public boolean isDone() { + return isDone0(result); + } + + private static boolean isDone0(Object result) { + return result != null && result != UNCANCELLABLE; + } + + @Override + public boolean isSuccess() { + Object result = this.result; + if (result == null || result == UNCANCELLABLE) { + return false; + } + return !(result instanceof CauseHolder); + } + + @Override + public Throwable cause() { + Object result = this.result; + if (result instanceof CauseHolder) { + return ((CauseHolder) result).cause; + } + return null; + } + + @Override + public Promise addListener(GenericFutureListener> listener) { + if (listener == null) { + throw new NullPointerException("listener"); + } + + if (isDone()) { + notifyLateListener(listener); + return this; + } + + synchronized (this) { + if (!isDone()) { + if (listeners == null) { + listeners = listener; + } else { + if (listeners instanceof DefaultFutureListeners) { + ((DefaultFutureListeners) listeners).add(listener); + } else { + final GenericFutureListener> firstListener = + (GenericFutureListener>) listeners; + listeners = new DefaultFutureListeners(firstListener, listener); + } + } + return this; + } + } + + notifyLateListener(listener); + return this; + } + + @Override + public Promise addListeners(GenericFutureListener>... listeners) { + if (listeners == null) { + throw new NullPointerException("listeners"); + } + + for (GenericFutureListener> l: listeners) { + if (l == null) { + break; + } + addListener(l); + } + return this; + } + + @Override + public Promise removeListener(GenericFutureListener> listener) { + if (listener == null) { + throw new NullPointerException("listener"); + } + + if (isDone()) { + return this; + } + + synchronized (this) { + if (!isDone()) { + if (listeners instanceof DefaultFutureListeners) { + ((DefaultFutureListeners) listeners).remove(listener); + } else if (listeners == listener) { + listeners = null; + } + } + } + + return this; + } + + @Override + public Promise removeListeners(GenericFutureListener>... listeners) { + if (listeners == null) { + throw new NullPointerException("listeners"); + } + + for (GenericFutureListener> l: listeners) { + if (l == null) { + break; + } + removeListener(l); + } + return this; + } + + @Override + public Promise sync() throws InterruptedException { + await(); + rethrowIfFailed(); + return this; + } + + @Override + public Promise syncUninterruptibly() { + awaitUninterruptibly(); + rethrowIfFailed(); + return this; + } + + private void rethrowIfFailed() { + Throwable cause = cause(); + if (cause == null) { + return; + } + + PlatformDependent.throwException(cause); + } + + @Override + public Promise await() throws InterruptedException { + if (isDone()) { + return this; + } + + if (Thread.interrupted()) { + throw new InterruptedException(toString()); + } + + synchronized (this) { + while (!isDone()) { + checkDeadLock(); + incWaiters(); + try { + wait(); + } finally { + decWaiters(); + } + } + } + return this; + } + + @Override + public boolean await(long timeout, TimeUnit unit) + throws InterruptedException { + return await0(unit.toNanos(timeout), true); + } + + @Override + public boolean await(long timeoutMillis) throws InterruptedException { + return await0(MILLISECONDS.toNanos(timeoutMillis), true); + } + + @Override + public Promise awaitUninterruptibly() { + if (isDone()) { + return this; + } + + boolean interrupted = false; + synchronized (this) { + while (!isDone()) { + checkDeadLock(); + incWaiters(); + try { + wait(); + } catch (InterruptedException e) { + // Interrupted while waiting. + interrupted = true; + } finally { + decWaiters(); + } + } + } + + if (interrupted) { + Thread.currentThread().interrupt(); + } + + return this; + } + + @Override + public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { + try { + return await0(unit.toNanos(timeout), false); + } catch (InterruptedException e) { + // Should not be raised at all. + throw new InternalError(); + } + } + + @Override + public boolean awaitUninterruptibly(long timeoutMillis) { + try { + return await0(MILLISECONDS.toNanos(timeoutMillis), false); + } catch (InterruptedException e) { + // Should not be raised at all. + throw new InternalError(); + } + } + + private boolean await0(long timeoutNanos, boolean interruptable) throws InterruptedException { + if (isDone()) { + return true; + } + + if (timeoutNanos <= 0) { + return isDone(); + } + + if (interruptable && Thread.interrupted()) { + throw new InterruptedException(toString()); + } + + long startTime = System.nanoTime(); + long waitTime = timeoutNanos; + boolean interrupted = false; + + try { + synchronized (this) { + if (isDone()) { + return true; + } + + if (waitTime <= 0) { + return isDone(); + } + + checkDeadLock(); + incWaiters(); + try { + for (;;) { + try { + wait(waitTime / 1000000, (int) (waitTime % 1000000)); + } catch (InterruptedException e) { + if (interruptable) { + throw e; + } else { + interrupted = true; + } + } + + if (isDone()) { + return true; + } else { + waitTime = timeoutNanos - (System.nanoTime() - startTime); + if (waitTime <= 0) { + return isDone(); + } + } + } + } finally { + decWaiters(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Do deadlock checks + */ + protected void checkDeadLock() { + EventExecutor e = executor(); + if (e != null && e.inEventLoop()) { + throw new BlockingOperationException(toString()); + } + } + + @Override + public Promise setSuccess(V result) { + if (setSuccess0(result)) { + notifyListeners(); + return this; + } + throw new IllegalStateException("complete already: " + this); + } + + @Override + public boolean trySuccess(V result) { + if (setSuccess0(result)) { + notifyListeners(); + return true; + } + return false; + } + + @Override + public Promise setFailure(Throwable cause) { + if (setFailure0(cause)) { + notifyListeners(); + return this; + } + throw new IllegalStateException("complete already: " + this, cause); + } + + @Override + public boolean tryFailure(Throwable cause) { + if (setFailure0(cause)) { + notifyListeners(); + return true; + } + return false; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + Object result = this.result; + if (isDone0(result) || result == UNCANCELLABLE) { + return false; + } + + synchronized (this) { + // Allow only once. + result = this.result; + if (isDone0(result) || result == UNCANCELLABLE) { + return false; + } + + this.result = CANCELLATION_CAUSE_HOLDER; + if (hasWaiters()) { + notifyAll(); + } + } + + notifyListeners(); + return true; + } + + @Override + public boolean setUncancellable() { + Object result = this.result; + if (isDone0(result)) { + return !isCancelled0(result); + } + + synchronized (this) { + // Allow only once. + result = this.result; + if (isDone0(result)) { + return !isCancelled0(result); + } + + this.result = UNCANCELLABLE; + } + return true; + } + + private boolean setFailure0(Throwable cause) { + if (cause == null) { + throw new NullPointerException("cause"); + } + + if (isDone()) { + return false; + } + + synchronized (this) { + // Allow only once. + if (isDone()) { + return false; + } + + result = new CauseHolder(cause); + if (hasWaiters()) { + notifyAll(); + } + } + return true; + } + + private boolean setSuccess0(V result) { + if (isDone()) { + return false; + } + + synchronized (this) { + // Allow only once. + if (isDone()) { + return false; + } + if (result == null) { + this.result = SUCCESS; + } else { + this.result = result; + } + if (hasWaiters()) { + notifyAll(); + } + } + return true; + } + + @Override + + public V getNow() { + Object result = this.result; + if (result instanceof CauseHolder || result == SUCCESS) { + return null; + } + return (V) result; + } + + private boolean hasWaiters() { + return waiters > 0; + } + + private void incWaiters() { + if (waiters == Short.MAX_VALUE) { + throw new IllegalStateException("too many waiters: " + this); + } + waiters ++; + } + + private void decWaiters() { + waiters --; + } + + private void notifyListeners() { + // This method doesn't need synchronization because: + // 1) This method is always called after synchronized (this) block. + // Hence any listener list modification happens-before this method. + // 2) This method is called only when 'done' is true. Once 'done' + // becomes true, the listener list is never modified - see add/removeListener() + + Object listeners = this.listeners; + if (listeners == null) { + return; + } + + EventExecutor executor = executor(); + if (executor.inEventLoop()) { + final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get(); + final int stackDepth = threadLocals.futureListenerStackDepth(); + if (stackDepth < MAX_LISTENER_STACK_DEPTH) { + threadLocals.setFutureListenerStackDepth(stackDepth + 1); + try { + if (listeners instanceof DefaultFutureListeners) { + notifyListeners0(this, (DefaultFutureListeners) listeners); + } else { + final GenericFutureListener> l = + (GenericFutureListener>) listeners; + notifyListener0(this, l); + } + } finally { + this.listeners = null; + threadLocals.setFutureListenerStackDepth(stackDepth); + } + return; + } + } + + if (listeners instanceof DefaultFutureListeners) { + final DefaultFutureListeners dfl = (DefaultFutureListeners) listeners; + execute(executor, new Runnable() { + @Override + public void run() { + notifyListeners0(DefaultPromise.this, dfl); + DefaultPromise.this.listeners = null; + } + }); + } else { + final GenericFutureListener> l = + (GenericFutureListener>) listeners; + execute(executor, new Runnable() { + @Override + public void run() { + notifyListener0(DefaultPromise.this, l); + DefaultPromise.this.listeners = null; + } + }); + } + } + + private static void notifyListeners0(Future future, DefaultFutureListeners listeners) { + final GenericFutureListener[] a = listeners.listeners(); + final int size = listeners.size(); + for (int i = 0; i < size; i ++) { + notifyListener0(future, a[i]); + } + } + + /** + * Notifies the specified listener which were added after this promise is already done. + * This method ensures that the specified listener is not notified until {@link #listeners} becomes {@code null} + * to avoid the case where the late listeners are notified even before the early listeners are notified. + */ + private void notifyLateListener(final GenericFutureListener l) { + final EventExecutor executor = executor(); + if (executor.inEventLoop()) { + if (listeners == null && lateListeners == null) { + final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get(); + final int stackDepth = threadLocals.futureListenerStackDepth(); + if (stackDepth < MAX_LISTENER_STACK_DEPTH) { + threadLocals.setFutureListenerStackDepth(stackDepth + 1); + try { + notifyListener0(this, l); + } finally { + threadLocals.setFutureListenerStackDepth(stackDepth); + } + return; + } + } else { + LateListeners lateListeners = this.lateListeners; + if (lateListeners == null) { + this.lateListeners = lateListeners = new LateListeners(); + } + lateListeners.add(l); + execute(executor, lateListeners); + return; + } + } + + // Add the late listener to lateListeners in the executor thread for thread safety. + // We could just make LateListeners extend ConcurrentLinkedQueue, but it's an overkill considering + // that most asynchronous applications won't execute this code path. + execute(executor, new LateListenerNotifier(l)); + } + + protected static void notifyListener( + final EventExecutor eventExecutor, final Future future, final GenericFutureListener l) { + + if (eventExecutor.inEventLoop()) { + final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get(); + final int stackDepth = threadLocals.futureListenerStackDepth(); + if (stackDepth < MAX_LISTENER_STACK_DEPTH) { + threadLocals.setFutureListenerStackDepth(stackDepth + 1); + try { + notifyListener0(future, l); + } finally { + threadLocals.setFutureListenerStackDepth(stackDepth); + } + return; + } + } + + execute(eventExecutor, new Runnable() { + @Override + public void run() { + notifyListener0(future, l); + } + }); + } + + private static void execute(EventExecutor executor, Runnable task) { + try { + executor.execute(task); + } catch (Throwable t) { + rejectedExecutionLogger.error("Failed to submit a listener notification task. Event loop shut down?", t); + } + } + + + static void notifyListener0(Future future, GenericFutureListener l) { + try { + l.operationComplete(future); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t); + } + } + } + + /** + * Returns a {@link GenericProgressiveFutureListener}, an array of {@link GenericProgressiveFutureListener}, or + * {@code null}. + */ + private synchronized Object progressiveListeners() { + Object listeners = this.listeners; + if (listeners == null) { + // No listeners added + return null; + } + + if (listeners instanceof DefaultFutureListeners) { + // Copy DefaultFutureListeners into an array of listeners. + DefaultFutureListeners dfl = (DefaultFutureListeners) listeners; + int progressiveSize = dfl.progressiveSize(); + switch (progressiveSize) { + case 0: + return null; + case 1: + for (GenericFutureListener l: dfl.listeners()) { + if (l instanceof GenericProgressiveFutureListener) { + return l; + } + } + return null; + } + + GenericFutureListener[] array = dfl.listeners(); + GenericProgressiveFutureListener[] copy = new GenericProgressiveFutureListener[progressiveSize]; + for (int i = 0, j = 0; j < progressiveSize; i ++) { + GenericFutureListener l = array[i]; + if (l instanceof GenericProgressiveFutureListener) { + copy[j ++] = (GenericProgressiveFutureListener) l; + } + } + + return copy; + } else if (listeners instanceof GenericProgressiveFutureListener) { + return listeners; + } else { + // Only one listener was added and it's not a progressive listener. + return null; + } + } + + + void notifyProgressiveListeners(final long progress, final long total) { + final Object listeners = progressiveListeners(); + if (listeners == null) { + return; + } + + final ProgressiveFuture self = (ProgressiveFuture) this; + + EventExecutor executor = executor(); + if (executor.inEventLoop()) { + if (listeners instanceof GenericProgressiveFutureListener[]) { + notifyProgressiveListeners0( + self, (GenericProgressiveFutureListener[]) listeners, progress, total); + } else { + notifyProgressiveListener0( + self, (GenericProgressiveFutureListener>) listeners, progress, total); + } + } else { + if (listeners instanceof GenericProgressiveFutureListener[]) { + final GenericProgressiveFutureListener[] array = + (GenericProgressiveFutureListener[]) listeners; + execute(executor, new Runnable() { + @Override + public void run() { + notifyProgressiveListeners0(self, array, progress, total); + } + }); + } else { + final GenericProgressiveFutureListener> l = + (GenericProgressiveFutureListener>) listeners; + execute(executor, new Runnable() { + @Override + public void run() { + notifyProgressiveListener0(self, l, progress, total); + } + }); + } + } + } + + private static void notifyProgressiveListeners0( + ProgressiveFuture future, GenericProgressiveFutureListener[] listeners, long progress, long total) { + for (GenericProgressiveFutureListener l: listeners) { + if (l == null) { + break; + } + notifyProgressiveListener0(future, l, progress, total); + } + } + + + private static void notifyProgressiveListener0( + ProgressiveFuture future, GenericProgressiveFutureListener l, long progress, long total) { + try { + l.operationProgressed(future, progress, total); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationProgressed()", t); + } + } + } + + private static final class CauseHolder { + final Throwable cause; + CauseHolder(Throwable cause) { + this.cause = cause; + } + } + + @Override + public String toString() { + return toStringBuilder().toString(); + } + + protected StringBuilder toStringBuilder() { + StringBuilder buf = new StringBuilder(64); + buf.append(StringUtil.simpleClassName(this)); + buf.append('@'); + buf.append(Integer.toHexString(hashCode())); + + Object result = this.result; + if (result == SUCCESS) { + buf.append("(success)"); + } else if (result == UNCANCELLABLE) { + buf.append("(uncancellable)"); + } else if (result instanceof CauseHolder) { + buf.append("(failure("); + buf.append(((CauseHolder) result).cause); + buf.append(')'); + } else { + buf.append("(incomplete)"); + } + return buf; + } + + private final class LateListeners extends ArrayDeque> implements Runnable { + + private static final long serialVersionUID = -687137418080392244L; + + LateListeners() { + super(2); + } + + @Override + public void run() { + if (listeners == null) { + for (;;) { + GenericFutureListener l = poll(); + if (l == null) { + break; + } + notifyListener0(DefaultPromise.this, l); + } + } else { + // Reschedule until the initial notification is done to avoid the race condition + // where the notification is made in an incorrect order. + execute(executor(), this); + } + } + } + + private final class LateListenerNotifier implements Runnable { + private GenericFutureListener l; + + LateListenerNotifier(GenericFutureListener l) { + this.l = l; + } + + @Override + public void run() { + LateListeners lateListeners = DefaultPromise.this.lateListeners; + if (l != null) { + if (lateListeners == null) { + DefaultPromise.this.lateListeners = lateListeners = new LateListeners(); + } + lateListeners.add(l); + l = null; + } + + lateListeners.run(); + } + } +} diff --git a/common/src/common/net/util/concurrent/DefaultThreadFactory.java b/common/src/common/net/util/concurrent/DefaultThreadFactory.java new file mode 100644 index 0000000..8dc939d --- /dev/null +++ b/common/src/common/net/util/concurrent/DefaultThreadFactory.java @@ -0,0 +1,143 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.Locale; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import common.net.util.internal.StringUtil; + +/** + * A {@link ThreadFactory} implementation with a simple naming rule. + */ +public class DefaultThreadFactory implements ThreadFactory { + + private static final AtomicInteger poolId = new AtomicInteger(); + + private final AtomicInteger nextId = new AtomicInteger(); + private final String prefix; + private final boolean daemon; + private final int priority; + + public DefaultThreadFactory(Class poolType) { + this(poolType, false, Thread.NORM_PRIORITY); + } + + public DefaultThreadFactory(String poolName) { + this(poolName, false, Thread.NORM_PRIORITY); + } + + public DefaultThreadFactory(Class poolType, boolean daemon) { + this(poolType, daemon, Thread.NORM_PRIORITY); + } + + public DefaultThreadFactory(String poolName, boolean daemon) { + this(poolName, daemon, Thread.NORM_PRIORITY); + } + + public DefaultThreadFactory(Class poolType, int priority) { + this(poolType, false, priority); + } + + public DefaultThreadFactory(String poolName, int priority) { + this(poolName, false, priority); + } + + public DefaultThreadFactory(Class poolType, boolean daemon, int priority) { + this(toPoolName(poolType), daemon, priority); + } + + private static String toPoolName(Class poolType) { + if (poolType == null) { + throw new NullPointerException("poolType"); + } + + String poolName = StringUtil.simpleClassName(poolType); + switch (poolName.length()) { + case 0: + return "unknown"; + case 1: + return poolName.toLowerCase(Locale.US); + default: + if (Character.isUpperCase(poolName.charAt(0)) && Character.isLowerCase(poolName.charAt(1))) { + return Character.toLowerCase(poolName.charAt(0)) + poolName.substring(1); + } else { + return poolName; + } + } + } + + public DefaultThreadFactory(String poolName, boolean daemon, int priority) { + if (poolName == null) { + throw new NullPointerException("poolName"); + } + if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) { + throw new IllegalArgumentException( + "priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)"); + } + + prefix = poolName + '-' + poolId.incrementAndGet() + '-'; + this.daemon = daemon; + this.priority = priority; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet()); + try { + if (t.isDaemon()) { + if (!daemon) { + t.setDaemon(false); + } + } else { + if (daemon) { + t.setDaemon(true); + } + } + + if (t.getPriority() != priority) { + t.setPriority(priority); + } + } catch (Exception ignored) { + // Doesn't matter even if failed to set. + } + return t; + } + + protected Thread newThread(Runnable r, String name) { + return new FastThreadLocalThread(r, name); + } + + private static final class DefaultRunnableDecorator implements Runnable { + + private final Runnable r; + + DefaultRunnableDecorator(Runnable r) { + this.r = r; + } + + @Override + public void run() { + try { + r.run(); + } finally { + FastThreadLocal.removeAll(); + } + } + } +} diff --git a/common/src/common/net/util/concurrent/EventExecutor.java b/common/src/common/net/util/concurrent/EventExecutor.java new file mode 100644 index 0000000..e145548 --- /dev/null +++ b/common/src/common/net/util/concurrent/EventExecutor.java @@ -0,0 +1,72 @@ +/* + * 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 common.net.util.concurrent; + +/** + * The {@link EventExecutor} is a special {@link EventExecutorGroup} which comes + * with some handy methods to see if a {@link Thread} is executed in a event loop. + * Beside this it also extends the {@link EventExecutorGroup} to allow a generic way to + * access methods. + * + */ +public interface EventExecutor extends EventExecutorGroup { + + /** + * Returns a reference to itself. + */ + @Override + EventExecutor next(); + + /** + * Return the {@link EventExecutorGroup} which is the parent of this {@link EventExecutor}, + */ + EventExecutorGroup parent(); + + /** + * Calls {@link #inEventLoop(Thread)} with {@link Thread#currentThread()} as argument + */ + boolean inEventLoop(); + + /** + * Return {@code true} if the given {@link Thread} is executed in the event loop, + * {@code false} otherwise. + */ + boolean inEventLoop(Thread thread); + + /** + * Return a new {@link Promise}. + */ + Promise newPromise(); + + /** + * Create a new {@link ProgressivePromise}. + */ + ProgressivePromise newProgressivePromise(); + + /** + * Create a new {@link Future} which is marked as successes already. So {@link Future#isSuccess()} + * will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also + * every call of blocking methods will just return without blocking. + */ + Future newSucceededFuture(V result); + + /** + * Create a new {@link Future} which is marked as fakued already. So {@link Future#isSuccess()} + * will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also + * every call of blocking methods will just return without blocking. + */ + Future newFailedFuture(Throwable cause); +} diff --git a/common/src/common/net/util/concurrent/EventExecutorGroup.java b/common/src/common/net/util/concurrent/EventExecutorGroup.java new file mode 100644 index 0000000..16da3f8 --- /dev/null +++ b/common/src/common/net/util/concurrent/EventExecutorGroup.java @@ -0,0 +1,112 @@ +/* + * 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 common.net.util.concurrent; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * The {@link EventExecutorGroup} is responsible to provide {@link EventExecutor}'s to use via its + * {@link #next()} method. Beside this it also is responsible to handle their live-cycle and allows + * to shut them down in a global fashion. + * + */ +public interface EventExecutorGroup extends ScheduledExecutorService, Iterable { + + /** + * Returns {@code true} if and only if this executor was started to be + * {@linkplain #shutdownGracefully() shut down gracefuclly} or was {@linkplain #isShutdown() shut down}. + */ + boolean isShuttingDown(); + + /** + * Shortcut method for {@link #shutdownGracefully(long, long, TimeUnit)} with sensible default values. + * + * @return the {@link #terminationFuture()} + */ + Future shutdownGracefully(); + + /** + * Signals this executor that the caller wants the executor to be shut down. Once this method is called, + * {@link #isShuttingDown()} starts to return {@code true}, and the executor prepares to shut itself down. + * Unlike {@link #shutdown()}, graceful shutdown ensures that no tasks are submitted for 'the quiet period' + * (usually a couple seconds) before it shuts itself down. If a task is submitted during the quiet period, + * it is guaranteed to be accepted and the quiet period will start over. + * + * @param quietPeriod the quiet period as described in the documentation + * @param timeout the maximum amount of time to wait until the executor is {@linkplain #shutdown()} + * regardless if a task was submitted during the quiet period + * @param unit the unit of {@code quietPeriod} and {@code timeout} + * + * @return the {@link #terminationFuture()} + */ + Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit); + + /** + * Returns the {@link Future} which is notified when this executor has been terminated. + */ + Future terminationFuture(); + + /** + * @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead. + */ + @Override + @Deprecated + void shutdown(); + + /** + * @deprecated {@link #shutdownGracefully(long, long, TimeUnit)} or {@link #shutdownGracefully()} instead. + */ + @Override + @Deprecated + List shutdownNow(); + + /** + * Returns one of the {@link EventExecutor}s that belong to this group. + */ + EventExecutor next(); + + /** + * Returns a read-only {@link Iterator} over all {@link EventExecutor}, which are handled by this + * {@link EventExecutorGroup} at the time of invoke this method. + */ + @Override + Iterator iterator(); + + @Override + Future submit(Runnable task); + + @Override + Future submit(Runnable task, T result); + + @Override + Future submit(Callable task); + + @Override + ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit); + + @Override + ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit); + + @Override + ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); + + @Override + ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); +} diff --git a/common/src/common/net/util/concurrent/FailedFuture.java b/common/src/common/net/util/concurrent/FailedFuture.java new file mode 100644 index 0000000..72780ba --- /dev/null +++ b/common/src/common/net/util/concurrent/FailedFuture.java @@ -0,0 +1,69 @@ +/* + * 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 common.net.util.concurrent; + +import common.net.util.internal.PlatformDependent; + +/** + * The {@link CompleteFuture} which is failed already. It is + * recommended to use {@link EventExecutor#newFailedFuture(Throwable)} + * instead of calling the constructor of this future. + */ +public final class FailedFuture extends CompleteFuture { + + private final Throwable cause; + + /** + * Creates a new instance. + * + * @param executor the {@link EventExecutor} associated with this future + * @param cause the cause of failure + */ + public FailedFuture(EventExecutor executor, Throwable cause) { + super(executor); + if (cause == null) { + throw new NullPointerException("cause"); + } + this.cause = cause; + } + + @Override + public Throwable cause() { + return cause; + } + + @Override + public boolean isSuccess() { + return false; + } + + @Override + public Future sync() { + PlatformDependent.throwException(cause); + return this; + } + + @Override + public Future syncUninterruptibly() { + PlatformDependent.throwException(cause); + return this; + } + + @Override + public V getNow() { + return null; + } +} diff --git a/common/src/common/net/util/concurrent/FastThreadLocal.java b/common/src/common/net/util/concurrent/FastThreadLocal.java new file mode 100644 index 0000000..05ea6b5 --- /dev/null +++ b/common/src/common/net/util/concurrent/FastThreadLocal.java @@ -0,0 +1,244 @@ +/* + * Copyright 2014 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 common.net.util.concurrent; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + +import common.net.util.internal.InternalThreadLocalMap; +import common.net.util.internal.PlatformDependent; + +/** + * A special variant of {@link ThreadLocal} that yields higher access performan when accessed from a + * {@link FastThreadLocalThread}. + *

+ * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table, + * to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash + * table, and it is useful when accessed frequently. + *

+ * To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype. + * By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason. + *

+ * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires + * a special field to store the necessary state. An access by any other kind of thread falls back to a regular + * {@link ThreadLocal}. + *

+ * + * @param the type of the thread-local variable + * @see ThreadLocal + */ +public class FastThreadLocal { + + private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex(); + + /** + * Removes all {@link FastThreadLocal} variables bound to the current thread. This operation is useful when you + * are in a container environment, and you don't want to leave the thread local variables in the threads you do not + * manage. + */ + public static void removeAll() { + InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); + if (threadLocalMap == null) { + return; + } + + try { + Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); + if (v != null && v != InternalThreadLocalMap.UNSET) { + + Set> variablesToRemove = (Set>) v; + FastThreadLocal[] variablesToRemoveArray = + variablesToRemove.toArray(new FastThreadLocal[variablesToRemove.size()]); + for (FastThreadLocal tlv: variablesToRemoveArray) { + tlv.remove(threadLocalMap); + } + } + } finally { + InternalThreadLocalMap.remove(); + } + } + + /** + * Returns the number of thread local variables bound to the current thread. + */ + public static int size() { + InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); + if (threadLocalMap == null) { + return 0; + } else { + return threadLocalMap.size(); + } + } + + /** + * Destroys the data structure that keeps all {@link FastThreadLocal} variables accessed from + * non-{@link FastThreadLocalThread}s. This operation is useful when you are in a container environment, and you + * do not want to leave the thread local variables in the threads you do not manage. Call this method when your + * application is being unloaded from the container. + */ + public static void destroy() { + InternalThreadLocalMap.destroy(); + } + + + private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal variable) { + Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); + Set> variablesToRemove; + if (v == InternalThreadLocalMap.UNSET || v == null) { + variablesToRemove = Collections.newSetFromMap(new IdentityHashMap, Boolean>()); + threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove); + } else { + variablesToRemove = (Set>) v; + } + + variablesToRemove.add(variable); + } + + private static void removeFromVariablesToRemove( + InternalThreadLocalMap threadLocalMap, FastThreadLocal variable) { + + Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); + + if (v == InternalThreadLocalMap.UNSET || v == null) { + return; + } + + + Set> variablesToRemove = (Set>) v; + variablesToRemove.remove(variable); + } + + private final int index; + + public FastThreadLocal() { + index = InternalThreadLocalMap.nextVariableIndex(); + } + + /** + * Returns the current value for the current thread + */ + public final V get() { + return get(InternalThreadLocalMap.get()); + } + + /** + * Returns the current value for the specified thread local map. + * The specified thread local map must be for the current thread. + */ + + public final V get(InternalThreadLocalMap threadLocalMap) { + Object v = threadLocalMap.indexedVariable(index); + if (v != InternalThreadLocalMap.UNSET) { + return (V) v; + } + + return initialize(threadLocalMap); + } + + private V initialize(InternalThreadLocalMap threadLocalMap) { + V v = null; + try { + v = initialValue(); + } catch (Exception e) { + PlatformDependent.throwException(e); + } + + threadLocalMap.setIndexedVariable(index, v); + addToVariablesToRemove(threadLocalMap, this); + return v; + } + + /** + * Set the value for the current thread. + */ + public final void set(V value) { + if (value != InternalThreadLocalMap.UNSET) { + set(InternalThreadLocalMap.get(), value); + } else { + remove(); + } + } + + /** + * Set the value for the specified thread local map. The specified thread local map must be for the current thread. + */ + public final void set(InternalThreadLocalMap threadLocalMap, V value) { + if (value != InternalThreadLocalMap.UNSET) { + if (threadLocalMap.setIndexedVariable(index, value)) { + addToVariablesToRemove(threadLocalMap, this); + } + } else { + remove(threadLocalMap); + } + } + + /** + * Returns {@code true} if and only if this thread-local variable is set. + */ + public final boolean isSet() { + return isSet(InternalThreadLocalMap.getIfSet()); + } + + /** + * Returns {@code true} if and only if this thread-local variable is set. + * The specified thread local map must be for the current thread. + */ + public final boolean isSet(InternalThreadLocalMap threadLocalMap) { + return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index); + } + /** + * Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue(). + */ + public final void remove() { + remove(InternalThreadLocalMap.getIfSet()); + } + + /** + * Sets the value to uninitialized for the specified thread local map; + * a proceeding call to get() will trigger a call to initialValue(). + * The specified thread local map must be for the current thread. + */ + + public final void remove(InternalThreadLocalMap threadLocalMap) { + if (threadLocalMap == null) { + return; + } + + Object v = threadLocalMap.removeIndexedVariable(index); + removeFromVariablesToRemove(threadLocalMap, this); + + if (v != InternalThreadLocalMap.UNSET) { + try { + onRemoval((V) v); + } catch (Exception e) { + PlatformDependent.throwException(e); + } + } + } + + /** + * Returns the initial value for this thread-local variable. + */ + protected V initialValue() throws Exception { + return null; + } + + /** + * Invoked when this thread local variable is removed by {@link #remove()}. + */ + protected void onRemoval( V value) throws Exception { } +} diff --git a/common/src/common/net/util/concurrent/FastThreadLocalThread.java b/common/src/common/net/util/concurrent/FastThreadLocalThread.java new file mode 100644 index 0000000..e81083f --- /dev/null +++ b/common/src/common/net/util/concurrent/FastThreadLocalThread.java @@ -0,0 +1,72 @@ +/* +* Copyright 2014 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 common.net.util.concurrent; + +import common.net.util.internal.InternalThreadLocalMap; + +/** + * A special {@link Thread} that provides fast access to {@link FastThreadLocal} variables. + */ +public class FastThreadLocalThread extends Thread { + + private InternalThreadLocalMap threadLocalMap; + + public FastThreadLocalThread() { } + + public FastThreadLocalThread(Runnable target) { + super(target); + } + + public FastThreadLocalThread(ThreadGroup group, Runnable target) { + super(group, target); + } + + public FastThreadLocalThread(String name) { + super(name); + } + + public FastThreadLocalThread(ThreadGroup group, String name) { + super(group, name); + } + + public FastThreadLocalThread(Runnable target, String name) { + super(target, name); + } + + public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) { + super(group, target, name); + } + + public FastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) { + super(group, target, name, stackSize); + } + + /** + * Returns the internal data structure that keeps the thread-local variables bound to this thread. + * Note that this method is for internal use only, and thus is subject to change at any time. + */ + public final InternalThreadLocalMap threadLocalMap() { + return threadLocalMap; + } + + /** + * Sets the internal data structure that keeps the thread-local variables bound to this thread. + * Note that this method is for internal use only, and thus is subject to change at any time. + */ + public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) { + this.threadLocalMap = threadLocalMap; + } +} diff --git a/common/src/common/net/util/concurrent/Future.java b/common/src/common/net/util/concurrent/Future.java new file mode 100644 index 0000000..37748cd --- /dev/null +++ b/common/src/common/net/util/concurrent/Future.java @@ -0,0 +1,168 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.concurrent.TimeUnit; + + +/** + * The result of an asynchronous operation. + */ + +public interface Future extends java.util.concurrent.Future { + + /** + * Returns {@code true} if and only if the I/O operation was completed + * successfully. + */ + boolean isSuccess(); + + /** + * returns {@code true} if and only if the operation can be cancelled via {@link #cancel(boolean)}. + */ + boolean isCancellable(); + + /** + * Returns the cause of the failed I/O operation if the I/O operation has + * failed. + * + * @return the cause of the failure. + * {@code null} if succeeded or this future is not + * completed yet. + */ + Throwable cause(); + + /** + * Adds the specified listener to this future. The + * specified listener is notified when this future is + * {@linkplain #isDone() done}. If this future is already + * completed, the specified listener is notified immediately. + */ + Future addListener(GenericFutureListener> listener); + + /** + * Adds the specified listeners to this future. The + * specified listeners are notified when this future is + * {@linkplain #isDone() done}. If this future is already + * completed, the specified listeners are notified immediately. + */ + Future addListeners(GenericFutureListener>... listeners); + + /** + * Removes the specified listener from this future. + * The specified listener is no longer notified when this + * future is {@linkplain #isDone() done}. If the specified + * listener is not associated with this future, this method + * does nothing and returns silently. + */ + Future removeListener(GenericFutureListener> listener); + + /** + * Removes the specified listeners from this future. + * The specified listeners are no longer notified when this + * future is {@linkplain #isDone() done}. If the specified + * listeners are not associated with this future, this method + * does nothing and returns silently. + */ + Future removeListeners(GenericFutureListener>... listeners); + + /** + * Waits for this future until it is done, and rethrows the cause of the failure if this future + * failed. + */ + Future sync() throws InterruptedException; + + /** + * Waits for this future until it is done, and rethrows the cause of the failure if this future + * failed. + */ + Future syncUninterruptibly(); + + /** + * Waits for this future to be completed. + * + * @throws InterruptedException + * if the current thread was interrupted + */ + Future await() throws InterruptedException; + + /** + * Waits for this future to be completed without + * interruption. This method catches an {@link InterruptedException} and + * discards it silently. + */ + Future awaitUninterruptibly(); + + /** + * Waits for this future to be completed within the + * specified time limit. + * + * @return {@code true} if and only if the future was completed within + * the specified time limit + * + * @throws InterruptedException + * if the current thread was interrupted + */ + boolean await(long timeout, TimeUnit unit) throws InterruptedException; + + /** + * Waits for this future to be completed within the + * specified time limit. + * + * @return {@code true} if and only if the future was completed within + * the specified time limit + * + * @throws InterruptedException + * if the current thread was interrupted + */ + boolean await(long timeoutMillis) throws InterruptedException; + + /** + * Waits for this future to be completed within the + * specified time limit without interruption. This method catches an + * {@link InterruptedException} and discards it silently. + * + * @return {@code true} if and only if the future was completed within + * the specified time limit + */ + boolean awaitUninterruptibly(long timeout, TimeUnit unit); + + /** + * Waits for this future to be completed within the + * specified time limit without interruption. This method catches an + * {@link InterruptedException} and discards it silently. + * + * @return {@code true} if and only if the future was completed within + * the specified time limit + */ + boolean awaitUninterruptibly(long timeoutMillis); + + /** + * Return the result without blocking. If the future is not done yet this will return {@code null}. + * + * As it is possible that a {@code null} value is used to mark the future as successful you also need to check + * if the future is really done with {@link #isDone()} and not relay on the returned {@code null} value. + */ + V getNow(); + + /** + * {@inheritDoc} + * + * If the cancellation was successful it will fail the future with an {@link CancellationException}. + */ + @Override + boolean cancel(boolean mayInterruptIfRunning); +} diff --git a/common/src/common/net/util/concurrent/FutureListener.java b/common/src/common/net/util/concurrent/FutureListener.java new file mode 100644 index 0000000..8d5a690 --- /dev/null +++ b/common/src/common/net/util/concurrent/FutureListener.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +/** + * A subtype of {@link GenericFutureListener} that hides type parameter for convenience. + *
+ * Future f = new DefaultPromise(..);
+ * f.addListener(new FutureListener() {
+ *     public void operationComplete(Future f) { .. }
+ * });
+ * 
+ */ +public interface FutureListener extends GenericFutureListener> { } diff --git a/common/src/common/net/util/concurrent/GenericFutureListener.java b/common/src/common/net/util/concurrent/GenericFutureListener.java new file mode 100644 index 0000000..a5d46cc --- /dev/null +++ b/common/src/common/net/util/concurrent/GenericFutureListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.EventListener; + +/** + * Listens to the result of a {@link Future}. The result of the asynchronous operation is notified once this listener + * is added by calling {@link Future#addListener(GenericFutureListener)}. + */ +public interface GenericFutureListener> extends EventListener { + + /** + * Invoked when the operation associated with the {@link Future} has been completed. + * + * @param future the source {@link Future} which called this callback + */ + void operationComplete(F future) throws Exception; +} diff --git a/common/src/common/net/util/concurrent/GenericProgressiveFutureListener.java b/common/src/common/net/util/concurrent/GenericProgressiveFutureListener.java new file mode 100644 index 0000000..8da4cbb --- /dev/null +++ b/common/src/common/net/util/concurrent/GenericProgressiveFutureListener.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +public interface GenericProgressiveFutureListener> extends GenericFutureListener { + /** + * Invoked when the operation has progressed. + * + * @param progress the progress of the operation so far (cumulative) + * @param total the number that signifies the end of the operation when {@code progress} reaches at it. + * {@code -1} if the end of operation is unknown. + */ + void operationProgressed(F future, long progress, long total) throws Exception; +} diff --git a/common/src/common/net/util/concurrent/GlobalEventExecutor.java b/common/src/common/net/util/concurrent/GlobalEventExecutor.java new file mode 100644 index 0000000..103143b --- /dev/null +++ b/common/src/common/net/util/concurrent/GlobalEventExecutor.java @@ -0,0 +1,391 @@ +/* + * 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 common.net.util.concurrent; + +import java.util.Iterator; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Single-thread singleton {@link EventExecutor}. It starts the thread automatically and stops it when there is no + * task pending in the task queue for 1 second. Please note it is not scalable to schedule large number of tasks to + * this executor; use a dedicated executor. + */ +public final class GlobalEventExecutor extends AbstractEventExecutor { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(GlobalEventExecutor.class); + + private static final long SCHEDULE_PURGE_INTERVAL = TimeUnit.SECONDS.toNanos(1); + + public static final GlobalEventExecutor INSTANCE = new GlobalEventExecutor(); + + final BlockingQueue taskQueue = new LinkedBlockingQueue(); + final Queue> delayedTaskQueue = new PriorityQueue>(); + final ScheduledFutureTask purgeTask = new ScheduledFutureTask( + this, delayedTaskQueue, Executors.callable(new PurgeTask(), null), + ScheduledFutureTask.deadlineNanos(SCHEDULE_PURGE_INTERVAL), -SCHEDULE_PURGE_INTERVAL); + + private final ThreadFactory threadFactory = new DefaultThreadFactory(getClass()); + private final TaskRunner taskRunner = new TaskRunner(); + private final AtomicBoolean started = new AtomicBoolean(); + volatile Thread thread; + + private final Future terminationFuture = new FailedFuture(this, new UnsupportedOperationException()); + + private GlobalEventExecutor() { + delayedTaskQueue.add(purgeTask); + } + + @Override + public EventExecutorGroup parent() { + return null; + } + + /** + * Take the next {@link Runnable} from the task queue and so will block if no task is currently present. + * + * @return {@code null} if the executor thread has been interrupted or waken up. + */ + Runnable takeTask() { + BlockingQueue taskQueue = this.taskQueue; + for (;;) { + ScheduledFutureTask delayedTask = delayedTaskQueue.peek(); + if (delayedTask == null) { + Runnable task = null; + try { + task = taskQueue.take(); + } catch (InterruptedException e) { + // Ignore + } + return task; + } else { + long delayNanos = delayedTask.delayNanos(); + Runnable task; + if (delayNanos > 0) { + try { + task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + return null; + } + } else { + task = taskQueue.poll(); + } + + if (task == null) { + fetchFromDelayedQueue(); + task = taskQueue.poll(); + } + + if (task != null) { + return task; + } + } + } + } + + private void fetchFromDelayedQueue() { + long nanoTime = 0L; + for (;;) { + ScheduledFutureTask delayedTask = delayedTaskQueue.peek(); + if (delayedTask == null) { + break; + } + + if (nanoTime == 0L) { + nanoTime = ScheduledFutureTask.nanoTime(); + } + + if (delayedTask.deadlineNanos() <= nanoTime) { + delayedTaskQueue.remove(); + taskQueue.add(delayedTask); + } else { + break; + } + } + } + + /** + * Return the number of tasks that are pending for processing. + * + * Be aware that this operation may be expensive as it depends on the internal implementation of the + * SingleThreadEventExecutor. So use it was care! + */ + public int pendingTasks() { + return taskQueue.size(); + } + + /** + * Add a task to the task queue, or throws a {@link RejectedExecutionException} if this instance was shutdown + * before. + */ + private void addTask(Runnable task) { + if (task == null) { + throw new NullPointerException("task"); + } + taskQueue.add(task); + } + + @Override + public boolean inEventLoop(Thread thread) { + return thread == this.thread; + } + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + return terminationFuture(); + } + + @Override + public Future terminationFuture() { + return terminationFuture; + } + + @Override + @Deprecated + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShuttingDown() { + return false; + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return false; + } + + /** + * Waits until the worker thread of this executor has no tasks left in its task queue and terminates itself. + * Because a new worker thread will be started again when a new task is submitted, this operation is only useful + * when you want to ensure that the worker thread is terminated after your application is shut + * down and there's no chance of submitting a new task afterwards. + * + * @return {@code true} if and only if the worker thread has been terminated + */ + public boolean awaitInactivity(long timeout, TimeUnit unit) throws InterruptedException { + if (unit == null) { + throw new NullPointerException("unit"); + } + + final Thread thread = this.thread; + if (thread == null) { + throw new IllegalStateException("thread was not started"); + } + thread.join(unit.toMillis(timeout)); + return !thread.isAlive(); + } + + @Override + public void execute(Runnable task) { + if (task == null) { + throw new NullPointerException("task"); + } + + addTask(task); + if (!inEventLoop()) { + startThread(); + } + } + + // ScheduledExecutorService implementation + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + if (command == null) { + throw new NullPointerException("command"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (delay < 0) { + throw new IllegalArgumentException( + String.format("delay: %d (expected: >= 0)", delay)); + } + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + if (callable == null) { + throw new NullPointerException("callable"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (delay < 0) { + throw new IllegalArgumentException( + String.format("delay: %d (expected: >= 0)", delay)); + } + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, callable, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + if (command == null) { + throw new NullPointerException("command"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (initialDelay < 0) { + throw new IllegalArgumentException( + String.format("initialDelay: %d (expected: >= 0)", initialDelay)); + } + if (period <= 0) { + throw new IllegalArgumentException( + String.format("period: %d (expected: > 0)", period)); + } + + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, Executors.callable(command, null), + ScheduledFutureTask.deadlineNanos(unit.toNanos(initialDelay)), unit.toNanos(period))); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + if (command == null) { + throw new NullPointerException("command"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (initialDelay < 0) { + throw new IllegalArgumentException( + String.format("initialDelay: %d (expected: >= 0)", initialDelay)); + } + if (delay <= 0) { + throw new IllegalArgumentException( + String.format("delay: %d (expected: > 0)", delay)); + } + + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, Executors.callable(command, null), + ScheduledFutureTask.deadlineNanos(unit.toNanos(initialDelay)), -unit.toNanos(delay))); + } + + private ScheduledFuture schedule(final ScheduledFutureTask task) { + if (task == null) { + throw new NullPointerException("task"); + } + + if (inEventLoop()) { + delayedTaskQueue.add(task); + } else { + execute(new Runnable() { + @Override + public void run() { + delayedTaskQueue.add(task); + } + }); + } + + return task; + } + + private void startThread() { + if (started.compareAndSet(false, true)) { + Thread t = threadFactory.newThread(taskRunner); + t.start(); + thread = t; + } + } + + final class TaskRunner implements Runnable { + @Override + public void run() { + for (;;) { + Runnable task = takeTask(); + if (task != null) { + try { + task.run(); + } catch (Throwable t) { + logger.warn("Unexpected exception from the global event executor: ", t); + } + + if (task != purgeTask) { + continue; + } + } + + // Terminate if there is no task in the queue (except the purge task). + if (taskQueue.isEmpty() && delayedTaskQueue.size() == 1) { + // Mark the current thread as stopped. + // The following CAS must always success and must be uncontended, + // because only one thread should be running at the same time. + boolean stopped = started.compareAndSet(true, false); + assert stopped; + + // Check if there are pending entries added by execute() or schedule*() while we do CAS above. + if (taskQueue.isEmpty() && delayedTaskQueue.size() == 1) { + // A) No new task was added and thus there's nothing to handle + // -> safe to terminate because there's nothing left to do + // B) A new thread started and handled all the new tasks. + // -> safe to terminate the new thread will take care the rest + break; + } + + // There are pending tasks added again. + if (!started.compareAndSet(false, true)) { + // startThread() started a new thread and set 'started' to true. + // -> terminate this thread so that the new thread reads from taskQueue exclusively. + break; + } + + // New tasks were added, but this worker was faster to set 'started' to true. + // i.e. a new worker thread was not started by startThread(). + // -> keep this thread alive to handle the newly added entries. + } + } + } + } + + private final class PurgeTask implements Runnable { + @Override + public void run() { + Iterator> i = delayedTaskQueue.iterator(); + while (i.hasNext()) { + ScheduledFutureTask task = i.next(); + if (task.isCancelled()) { + i.remove(); + } + } + } + } +} diff --git a/common/src/common/net/util/concurrent/ImmediateEventExecutor.java b/common/src/common/net/util/concurrent/ImmediateEventExecutor.java new file mode 100644 index 0000000..6b8fddb --- /dev/null +++ b/common/src/common/net/util/concurrent/ImmediateEventExecutor.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.concurrent.TimeUnit; + +/** + * {@link AbstractEventExecutor} which execute tasks in the callers thread. + */ +public final class ImmediateEventExecutor extends AbstractEventExecutor { + public static final ImmediateEventExecutor INSTANCE = new ImmediateEventExecutor(); + + private final Future terminationFuture = new FailedFuture( + GlobalEventExecutor.INSTANCE, new UnsupportedOperationException()); + + private ImmediateEventExecutor() { + // use static instance + } + + @Override + public EventExecutorGroup parent() { + return null; + } + + @Override + public boolean inEventLoop() { + return true; + } + + @Override + public boolean inEventLoop(Thread thread) { + return true; + } + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + return terminationFuture(); + } + + @Override + public Future terminationFuture() { + return terminationFuture; + } + + @Override + @Deprecated + public void shutdown() { } + + @Override + public boolean isShuttingDown() { + return false; + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return false; + } + + @Override + public void execute(Runnable command) { + if (command == null) { + throw new NullPointerException("command"); + } + command.run(); + } + + @Override + public Promise newPromise() { + return new ImmediatePromise(this); + } + + @Override + public ProgressivePromise newProgressivePromise() { + return new ImmediateProgressivePromise(this); + } + + static class ImmediatePromise extends DefaultPromise { + ImmediatePromise(EventExecutor executor) { + super(executor); + } + + @Override + protected void checkDeadLock() { + // No check + } + } + + static class ImmediateProgressivePromise extends DefaultProgressivePromise { + ImmediateProgressivePromise(EventExecutor executor) { + super(executor); + } + + @Override + protected void checkDeadLock() { + // No check + } + } +} diff --git a/common/src/common/net/util/concurrent/ImmediateExecutor.java b/common/src/common/net/util/concurrent/ImmediateExecutor.java new file mode 100644 index 0000000..6660c6d --- /dev/null +++ b/common/src/common/net/util/concurrent/ImmediateExecutor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.concurrent.Executor; + +/** + * {@link Executor} which execute tasks in the callers thread. + */ +public final class ImmediateExecutor implements Executor { + public static final ImmediateExecutor INSTANCE = new ImmediateExecutor(); + + private ImmediateExecutor() { + // use static instance + } + + @Override + public void execute(Runnable command) { + if (command == null) { + throw new NullPointerException("command"); + } + command.run(); + } +} diff --git a/common/src/common/net/util/concurrent/MultithreadEventExecutorGroup.java b/common/src/common/net/util/concurrent/MultithreadEventExecutorGroup.java new file mode 100644 index 0000000..39a72c3 --- /dev/null +++ b/common/src/common/net/util/concurrent/MultithreadEventExecutorGroup.java @@ -0,0 +1,233 @@ +/* + * 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 common.net.util.concurrent; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Abstract base class for {@link EventExecutorGroup} implementations that handles their tasks with multiple threads at + * the same time. + */ +public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup { + + private final EventExecutor[] children; + private final AtomicInteger childIndex = new AtomicInteger(); + private final AtomicInteger terminatedChildren = new AtomicInteger(); + private final Promise terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE); + private final EventExecutorChooser chooser; + + /** + * Create a new instance. + * + * @param nThreads the number of threads that will be used by this instance. + * @param threadFactory the ThreadFactory to use, or {@code null} if the default should be used. + * @param args arguments which will passed to each {@link #newChild(ThreadFactory, Object...)} call + */ + protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) { + if (nThreads <= 0) { + throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads)); + } + + if (threadFactory == null) { + threadFactory = newDefaultThreadFactory(); + } + + children = new SingleThreadEventExecutor[nThreads]; + if (isPowerOfTwo(children.length)) { + chooser = new PowerOfTwoEventExecutorChooser(); + } else { + chooser = new GenericEventExecutorChooser(); + } + + for (int i = 0; i < nThreads; i ++) { + boolean success = false; + try { + children[i] = newChild(threadFactory, args); + success = true; + } catch (Exception e) { + // TODO: Think about if this is a good exception type + throw new IllegalStateException("failed to create a child event loop", e); + } finally { + if (!success) { + for (int j = 0; j < i; j ++) { + children[j].shutdownGracefully(); + } + + for (int j = 0; j < i; j ++) { + EventExecutor e = children[j]; + try { + while (!e.isTerminated()) { + e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); + } + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + } + + final FutureListener terminationListener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (terminatedChildren.incrementAndGet() == children.length) { + terminationFuture.setSuccess(null); + } + } + }; + + for (EventExecutor e: children) { + e.terminationFuture().addListener(terminationListener); + } + } + + protected ThreadFactory newDefaultThreadFactory() { + return new DefaultThreadFactory(getClass()); + } + + @Override + public EventExecutor next() { + return chooser.next(); + } + + @Override + public Iterator iterator() { + return children().iterator(); + } + + /** + * Return the number of {@link EventExecutor} this implementation uses. This number is the maps + * 1:1 to the threads it use. + */ + public final int executorCount() { + return children.length; + } + + /** + * Return a safe-copy of all of the children of this group. + */ + protected Set children() { + Set children = Collections.newSetFromMap(new LinkedHashMap()); + Collections.addAll(children, this.children); + return children; + } + + /** + * Create a new EventExecutor which will later then accessible via the {@link #next()} method. This method will be + * called for each thread that will serve this {@link MultithreadEventExecutorGroup}. + * + */ + protected abstract EventExecutor newChild( + ThreadFactory threadFactory, Object... args) throws Exception; + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + for (EventExecutor l: children) { + l.shutdownGracefully(quietPeriod, timeout, unit); + } + return terminationFuture(); + } + + @Override + public Future terminationFuture() { + return terminationFuture; + } + + @Override + @Deprecated + public void shutdown() { + for (EventExecutor l: children) { + l.shutdown(); + } + } + + @Override + public boolean isShuttingDown() { + for (EventExecutor l: children) { + if (!l.isShuttingDown()) { + return false; + } + } + return true; + } + + @Override + public boolean isShutdown() { + for (EventExecutor l: children) { + if (!l.isShutdown()) { + return false; + } + } + return true; + } + + @Override + public boolean isTerminated() { + for (EventExecutor l: children) { + if (!l.isTerminated()) { + return false; + } + } + return true; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + long deadline = System.nanoTime() + unit.toNanos(timeout); + loop: for (EventExecutor l: children) { + for (;;) { + long timeLeft = deadline - System.nanoTime(); + if (timeLeft <= 0) { + break loop; + } + if (l.awaitTermination(timeLeft, TimeUnit.NANOSECONDS)) { + break; + } + } + } + return isTerminated(); + } + + private static boolean isPowerOfTwo(int val) { + return (val & -val) == val; + } + + private interface EventExecutorChooser { + EventExecutor next(); + } + + private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser { + @Override + public EventExecutor next() { + return children[childIndex.getAndIncrement() & children.length - 1]; + } + } + + private final class GenericEventExecutorChooser implements EventExecutorChooser { + @Override + public EventExecutor next() { + return children[Math.abs(childIndex.getAndIncrement() % children.length)]; + } + } +} diff --git a/common/src/common/net/util/concurrent/ProgressiveFuture.java b/common/src/common/net/util/concurrent/ProgressiveFuture.java new file mode 100644 index 0000000..71c7134 --- /dev/null +++ b/common/src/common/net/util/concurrent/ProgressiveFuture.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +/** + * A {@link Future} which is used to indicate the progress of an operation. + */ +public interface ProgressiveFuture extends Future { + + @Override + ProgressiveFuture addListener(GenericFutureListener> listener); + + @Override + ProgressiveFuture addListeners(GenericFutureListener>... listeners); + + @Override + ProgressiveFuture removeListener(GenericFutureListener> listener); + + @Override + ProgressiveFuture removeListeners(GenericFutureListener>... listeners); + + @Override + ProgressiveFuture sync() throws InterruptedException; + + @Override + ProgressiveFuture syncUninterruptibly(); + + @Override + ProgressiveFuture await() throws InterruptedException; + + @Override + ProgressiveFuture awaitUninterruptibly(); +} diff --git a/common/src/common/net/util/concurrent/ProgressivePromise.java b/common/src/common/net/util/concurrent/ProgressivePromise.java new file mode 100644 index 0000000..e6a2e35 --- /dev/null +++ b/common/src/common/net/util/concurrent/ProgressivePromise.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +/** + * Special {@link ProgressiveFuture} which is writable. + */ +public interface ProgressivePromise extends Promise, ProgressiveFuture { + + /** + * Sets the current progress of the operation and notifies the listeners that implement + * {@link GenericProgressiveFutureListener}. + */ + ProgressivePromise setProgress(long progress, long total); + + /** + * Tries to set the current progress of the operation and notifies the listeners that implement + * {@link GenericProgressiveFutureListener}. If the operation is already complete or the progress is out of range, + * this method does nothing but returning {@code false}. + */ + boolean tryProgress(long progress, long total); + + @Override + ProgressivePromise setSuccess(V result); + + @Override + ProgressivePromise setFailure(Throwable cause); + + @Override + ProgressivePromise addListener(GenericFutureListener> listener); + + @Override + ProgressivePromise addListeners(GenericFutureListener>... listeners); + + @Override + ProgressivePromise removeListener(GenericFutureListener> listener); + + @Override + ProgressivePromise removeListeners(GenericFutureListener>... listeners); + + @Override + ProgressivePromise await() throws InterruptedException; + + @Override + ProgressivePromise awaitUninterruptibly(); + + @Override + ProgressivePromise sync() throws InterruptedException; + + @Override + ProgressivePromise syncUninterruptibly(); +} diff --git a/common/src/common/net/util/concurrent/Promise.java b/common/src/common/net/util/concurrent/Promise.java new file mode 100644 index 0000000..252d895 --- /dev/null +++ b/common/src/common/net/util/concurrent/Promise.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +/** + * Special {@link Future} which is writable. + */ +public interface Promise extends Future { + + /** + * Marks this future as a success and notifies all + * listeners. + * + * If it is success or failed already it will throw an {@link IllegalStateException}. + */ + Promise setSuccess(V result); + + /** + * Marks this future as a success and notifies all + * listeners. + * + * @return {@code true} if and only if successfully marked this future as + * a success. Otherwise {@code false} because this future is + * already marked as either a success or a failure. + */ + boolean trySuccess(V result); + + /** + * Marks this future as a failure and notifies all + * listeners. + * + * If it is success or failed already it will throw an {@link IllegalStateException}. + */ + Promise setFailure(Throwable cause); + + /** + * Marks this future as a failure and notifies all + * listeners. + * + * @return {@code true} if and only if successfully marked this future as + * a failure. Otherwise {@code false} because this future is + * already marked as either a success or a failure. + */ + boolean tryFailure(Throwable cause); + + /** + * Make this future impossible to cancel. + * + * @return {@code true} if and only if successfully marked this future as uncancellable or it is already done + * without being cancelled. {@code false} if this future has been cancelled already. + */ + boolean setUncancellable(); + + @Override + Promise addListener(GenericFutureListener> listener); + + @Override + Promise addListeners(GenericFutureListener>... listeners); + + @Override + Promise removeListener(GenericFutureListener> listener); + + @Override + Promise removeListeners(GenericFutureListener>... listeners); + + @Override + Promise await() throws InterruptedException; + + @Override + Promise awaitUninterruptibly(); + + @Override + Promise sync() throws InterruptedException; + + @Override + Promise syncUninterruptibly(); +} diff --git a/common/src/common/net/util/concurrent/PromiseTask.java b/common/src/common/net/util/concurrent/PromiseTask.java new file mode 100644 index 0000000..8a897ad --- /dev/null +++ b/common/src/common/net/util/concurrent/PromiseTask.java @@ -0,0 +1,137 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.RunnableFuture; + +class PromiseTask extends DefaultPromise implements RunnableFuture { + + static Callable toCallable(Runnable runnable, T result) { + return new RunnableAdapter(runnable, result); + } + + private static final class RunnableAdapter implements Callable { + final Runnable task; + final T result; + + RunnableAdapter(Runnable task, T result) { + this.task = task; + this.result = result; + } + + @Override + public T call() { + task.run(); + return result; + } + + @Override + public String toString() { + return "Callable(task: " + task + ", result: " + result + ')'; + } + } + + protected final Callable task; + + PromiseTask(EventExecutor executor, Runnable runnable, V result) { + this(executor, toCallable(runnable, result)); + } + + PromiseTask(EventExecutor executor, Callable callable) { + super(executor); + task = callable; + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } + + @Override + public final boolean equals(Object obj) { + return this == obj; + } + + @Override + public void run() { + try { + if (setUncancellableInternal()) { + V result = task.call(); + setSuccessInternal(result); + } + } catch (Throwable e) { + setFailureInternal(e); + } + } + + @Override + public final Promise setFailure(Throwable cause) { + throw new IllegalStateException(); + } + + protected final Promise setFailureInternal(Throwable cause) { + super.setFailure(cause); + return this; + } + + @Override + public final boolean tryFailure(Throwable cause) { + return false; + } + + protected final boolean tryFailureInternal(Throwable cause) { + return super.tryFailure(cause); + } + + @Override + public final Promise setSuccess(V result) { + throw new IllegalStateException(); + } + + protected final Promise setSuccessInternal(V result) { + super.setSuccess(result); + return this; + } + + @Override + public final boolean trySuccess(V result) { + return false; + } + + protected final boolean trySuccessInternal(V result) { + return super.trySuccess(result); + } + + @Override + public final boolean setUncancellable() { + throw new IllegalStateException(); + } + + protected final boolean setUncancellableInternal() { + return super.setUncancellable(); + } + + @Override + protected StringBuilder toStringBuilder() { + StringBuilder buf = super.toStringBuilder(); + buf.setCharAt(buf.length() - 1, ','); + buf.append(" task: "); + buf.append(task); + buf.append(')'); + return buf; + } +} diff --git a/common/src/common/net/util/concurrent/ScheduledFuture.java b/common/src/common/net/util/concurrent/ScheduledFuture.java new file mode 100644 index 0000000..3498b13 --- /dev/null +++ b/common/src/common/net/util/concurrent/ScheduledFuture.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +/** + * The result of an scheduled asynchronous operation. + */ + +public interface ScheduledFuture extends Future, java.util.concurrent.ScheduledFuture { +} diff --git a/common/src/common/net/util/concurrent/ScheduledFutureTask.java b/common/src/common/net/util/concurrent/ScheduledFutureTask.java new file mode 100644 index 0000000..83f64b3 --- /dev/null +++ b/common/src/common/net/util/concurrent/ScheduledFutureTask.java @@ -0,0 +1,161 @@ +/* + * Copyright 2013 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 common.net.util.concurrent; + +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + + +final class ScheduledFutureTask extends PromiseTask implements ScheduledFuture { + private static final AtomicLong nextTaskId = new AtomicLong(); + private static final long START_TIME = System.nanoTime(); + + static long nanoTime() { + return System.nanoTime() - START_TIME; + } + + static long deadlineNanos(long delay) { + return nanoTime() + delay; + } + + private final long id = nextTaskId.getAndIncrement(); + private final Queue> delayedTaskQueue; + private long deadlineNanos; + /* 0 - no repeat, >0 - repeat at fixed rate, <0 - repeat with fixed delay */ + private final long periodNanos; + + ScheduledFutureTask( + EventExecutor executor, Queue> delayedTaskQueue, + Runnable runnable, V result, long nanoTime) { + + this(executor, delayedTaskQueue, toCallable(runnable, result), nanoTime); + } + + ScheduledFutureTask( + EventExecutor executor, Queue> delayedTaskQueue, + Callable callable, long nanoTime, long period) { + + super(executor, callable); + if (period == 0) { + throw new IllegalArgumentException("period: 0 (expected: != 0)"); + } + this.delayedTaskQueue = delayedTaskQueue; + deadlineNanos = nanoTime; + periodNanos = period; + } + + ScheduledFutureTask( + EventExecutor executor, Queue> delayedTaskQueue, + Callable callable, long nanoTime) { + + super(executor, callable); + this.delayedTaskQueue = delayedTaskQueue; + deadlineNanos = nanoTime; + periodNanos = 0; + } + + @Override + protected EventExecutor executor() { + return super.executor(); + } + + public long deadlineNanos() { + return deadlineNanos; + } + + public long delayNanos() { + return Math.max(0, deadlineNanos() - nanoTime()); + } + + public long delayNanos(long currentTimeNanos) { + return Math.max(0, deadlineNanos() - (currentTimeNanos - START_TIME)); + } + + @Override + public long getDelay(TimeUnit unit) { + return unit.convert(delayNanos(), TimeUnit.NANOSECONDS); + } + + @Override + public int compareTo(Delayed o) { + if (this == o) { + return 0; + } + + ScheduledFutureTask that = (ScheduledFutureTask) o; + long d = deadlineNanos() - that.deadlineNanos(); + if (d < 0) { + return -1; + } else if (d > 0) { + return 1; + } else if (id < that.id) { + return -1; + } else if (id == that.id) { + throw new Error(); + } else { + return 1; + } + } + + @Override + public void run() { + assert executor().inEventLoop(); + try { + if (periodNanos == 0) { + if (setUncancellableInternal()) { + V result = task.call(); + setSuccessInternal(result); + } + } else { + // check if is done as it may was cancelled + if (!isCancelled()) { + task.call(); + if (!executor().isShutdown()) { + long p = periodNanos; + if (p > 0) { + deadlineNanos += p; + } else { + deadlineNanos = nanoTime() - p; + } + if (!isCancelled()) { + delayedTaskQueue.add(this); + } + } + } + } + } catch (Throwable cause) { + setFailureInternal(cause); + } + } + + @Override + protected StringBuilder toStringBuilder() { + StringBuilder buf = super.toStringBuilder(); + buf.setCharAt(buf.length() - 1, ','); + buf.append(" id: "); + buf.append(id); + buf.append(", deadline: "); + buf.append(deadlineNanos); + buf.append(", period: "); + buf.append(periodNanos); + buf.append(')'); + return buf; + } +} diff --git a/common/src/common/net/util/concurrent/SingleThreadEventExecutor.java b/common/src/common/net/util/concurrent/SingleThreadEventExecutor.java new file mode 100644 index 0000000..4a635d1 --- /dev/null +++ b/common/src/common/net/util/concurrent/SingleThreadEventExecutor.java @@ -0,0 +1,870 @@ +/* + * 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 common.net.util.concurrent; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import common.net.util.internal.PlatformDependent; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * Abstract base class for {@link EventExecutor}'s that execute all its submitted tasks in a single thread. + * + */ +public abstract class SingleThreadEventExecutor extends AbstractEventExecutor { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SingleThreadEventExecutor.class); + + private static final int ST_NOT_STARTED = 1; + private static final int ST_STARTED = 2; + private static final int ST_SHUTTING_DOWN = 3; + private static final int ST_SHUTDOWN = 4; + private static final int ST_TERMINATED = 5; + + private static final Runnable WAKEUP_TASK = new Runnable() { + @Override + public void run() { + // Do nothing. + } + }; + + private static final AtomicIntegerFieldUpdater STATE_UPDATER; + + static { + AtomicIntegerFieldUpdater updater = + PlatformDependent.newAtomicIntegerFieldUpdater(SingleThreadEventExecutor.class, "state"); + if (updater == null) { + updater = AtomicIntegerFieldUpdater.newUpdater(SingleThreadEventExecutor.class, "state"); + } + STATE_UPDATER = updater; + } + + private final EventExecutorGroup parent; + private final Queue taskQueue; + final Queue> delayedTaskQueue = new PriorityQueue>(); + + private final Thread thread; + private final Semaphore threadLock = new Semaphore(0); + private final Set shutdownHooks = new LinkedHashSet(); + private final boolean addTaskWakesUp; + + private long lastExecutionTime; + + + private volatile int state = ST_NOT_STARTED; + + private volatile long gracefulShutdownQuietPeriod; + private volatile long gracefulShutdownTimeout; + private long gracefulShutdownStartTime; + + private final Promise terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE); + + /** + * Create a new instance + * + * @param parent the {@link EventExecutorGroup} which is the parent of this instance and belongs to it + * @param threadFactory the {@link ThreadFactory} which will be used for the used {@link Thread} + * @param addTaskWakesUp {@code true} if and only if invocation of {@link #addTask(Runnable)} will wake up the + * executor thread + */ + protected SingleThreadEventExecutor( + EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) { + + if (threadFactory == null) { + throw new NullPointerException("threadFactory"); + } + + this.parent = parent; + this.addTaskWakesUp = addTaskWakesUp; + + thread = threadFactory.newThread(new Runnable() { + @Override + public void run() { + boolean success = false; + updateLastExecutionTime(); + try { + SingleThreadEventExecutor.this.run(); + success = true; + } catch (Throwable t) { + logger.warn("Unexpected exception from an event executor: ", t); + } finally { + for (;;) { + int oldState = STATE_UPDATER.get(SingleThreadEventExecutor.this); + if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet( + SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) { + break; + } + } + // Check if confirmShutdown() was called at the end of the loop. + if (success && gracefulShutdownStartTime == 0) { + logger.error( + "Buggy " + EventExecutor.class.getSimpleName() + " implementation; " + + SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " + + "before run() implementation terminates."); + } + + try { + // Run all remaining tasks and shutdown hooks. + for (;;) { + if (confirmShutdown()) { + break; + } + } + } finally { + try { + cleanup(); + } finally { + STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED); + threadLock.release(); + if (!taskQueue.isEmpty()) { + logger.warn( + "An event executor terminated with " + + "non-empty task queue (" + taskQueue.size() + ')'); + } + + terminationFuture.setSuccess(null); + } + } + } + } + }); + + taskQueue = newTaskQueue(); + } + + /** + * Create a new {@link Queue} which will holds the tasks to execute. This default implementation will return a + * {@link LinkedBlockingQueue} but if your sub-class of {@link SingleThreadEventExecutor} will not do any blocking + * calls on the this {@link Queue} it may make sense to {@code @Override} this and return some more performant + * implementation that does not support blocking operations at all. + */ + protected Queue newTaskQueue() { + return new LinkedBlockingQueue(); + } + + @Override + public EventExecutorGroup parent() { + return parent; + } + + /** + * Interrupt the current running {@link Thread}. + */ + protected void interruptThread() { + thread.interrupt(); + } + + /** + * @see {@link Queue#poll()} + */ + protected Runnable pollTask() { + assert inEventLoop(); + for (;;) { + Runnable task = taskQueue.poll(); + if (task == WAKEUP_TASK) { + continue; + } + return task; + } + } + + /** + * Take the next {@link Runnable} from the task queue and so will block if no task is currently present. + *

+ * Be aware that this method will throw an {@link UnsupportedOperationException} if the task queue, which was + * created via {@link #newTaskQueue()}, does not implement {@link BlockingQueue}. + *

+ * + * @return {@code null} if the executor thread has been interrupted or waken up. + */ + protected Runnable takeTask() { + assert inEventLoop(); + if (!(taskQueue instanceof BlockingQueue)) { + throw new UnsupportedOperationException(); + } + + BlockingQueue taskQueue = (BlockingQueue) this.taskQueue; + for (;;) { + ScheduledFutureTask delayedTask = delayedTaskQueue.peek(); + if (delayedTask == null) { + Runnable task = null; + try { + task = taskQueue.take(); + if (task == WAKEUP_TASK) { + task = null; + } + } catch (InterruptedException e) { + // Ignore + } + return task; + } else { + long delayNanos = delayedTask.delayNanos(); + Runnable task = null; + if (delayNanos > 0) { + try { + task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + return null; + } + } + if (task == null) { + // We need to fetch the delayed tasks now as otherwise there may be a chance that + // delayed tasks are never executed if there is always one task in the taskQueue. + // This is for example true for the read task of OIO Transport + // See https://github.com/netty/netty/issues/1614 + fetchFromDelayedQueue(); + task = taskQueue.poll(); + } + + if (task != null) { + return task; + } + } + } + } + + private void fetchFromDelayedQueue() { + long nanoTime = 0L; + for (;;) { + ScheduledFutureTask delayedTask = delayedTaskQueue.peek(); + if (delayedTask == null) { + break; + } + + if (nanoTime == 0L) { + nanoTime = ScheduledFutureTask.nanoTime(); + } + + if (delayedTask.deadlineNanos() <= nanoTime) { + delayedTaskQueue.remove(); + taskQueue.add(delayedTask); + } else { + break; + } + } + } + + /** + * @see {@link Queue#peek()} + */ + protected Runnable peekTask() { + assert inEventLoop(); + return taskQueue.peek(); + } + + /** + * @see {@link Queue#isEmpty()} + */ + protected boolean hasTasks() { + assert inEventLoop(); + return !taskQueue.isEmpty(); + } + + /** + * Returns {@code true} if a scheduled task is ready for processing by {@link #runAllTasks()} or + * {@link #runAllTasks(long)}. + */ + protected boolean hasScheduledTasks() { + assert inEventLoop(); + ScheduledFutureTask delayedTask = delayedTaskQueue.peek(); + return delayedTask != null && delayedTask.deadlineNanos() <= ScheduledFutureTask.nanoTime(); + } + + /** + * Return the number of tasks that are pending for processing. + * + * Be aware that this operation may be expensive as it depends on the internal implementation of the + * SingleThreadEventExecutor. So use it was care! + */ + public final int pendingTasks() { + return taskQueue.size(); + } + + /** + * Add a task to the task queue, or throws a {@link RejectedExecutionException} if this instance was shutdown + * before. + */ + protected void addTask(Runnable task) { + if (task == null) { + throw new NullPointerException("task"); + } + if (isShutdown()) { + reject(); + } + taskQueue.add(task); + } + + /** + * @see {@link Queue#remove(Object)} + */ + protected boolean removeTask(Runnable task) { + if (task == null) { + throw new NullPointerException("task"); + } + return taskQueue.remove(task); + } + + /** + * Poll all tasks from the task queue and run them via {@link Runnable#run()} method. + * + * @return {@code true} if and only if at least one task was run + */ + protected boolean runAllTasks() { + fetchFromDelayedQueue(); + Runnable task = pollTask(); + if (task == null) { + return false; + } + + for (;;) { + try { + task.run(); + } catch (Throwable t) { + logger.warn("A task raised an exception.", t); + } + + task = pollTask(); + if (task == null) { + lastExecutionTime = ScheduledFutureTask.nanoTime(); + return true; + } + } + } + + /** + * Poll all tasks from the task queue and run them via {@link Runnable#run()} method. This method stops running + * the tasks in the task queue and returns if it ran longer than {@code timeoutNanos}. + */ + protected boolean runAllTasks(long timeoutNanos) { + fetchFromDelayedQueue(); + Runnable task = pollTask(); + if (task == null) { + return false; + } + + final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos; + long runTasks = 0; + long lastExecutionTime; + for (;;) { + try { + task.run(); + } catch (Throwable t) { + logger.warn("A task raised an exception.", t); + } + + runTasks ++; + + // Check timeout every 64 tasks because nanoTime() is relatively expensive. + // XXX: Hard-coded value - will make it configurable if it is really a problem. + if ((runTasks & 0x3F) == 0) { + lastExecutionTime = ScheduledFutureTask.nanoTime(); + if (lastExecutionTime >= deadline) { + break; + } + } + + task = pollTask(); + if (task == null) { + lastExecutionTime = ScheduledFutureTask.nanoTime(); + break; + } + } + + this.lastExecutionTime = lastExecutionTime; + return true; + } + + /** + * Returns the amount of time left until the scheduled task with the closest dead line is executed. + */ + protected long delayNanos(long currentTimeNanos) { + ScheduledFutureTask delayedTask = delayedTaskQueue.peek(); + if (delayedTask == null) { + return SCHEDULE_PURGE_INTERVAL; + } + + return delayedTask.delayNanos(currentTimeNanos); + } + + /** + * Updates the internal timestamp that tells when a submitted task was executed most recently. + * {@link #runAllTasks()} and {@link #runAllTasks(long)} updates this timestamp automatically, and thus there's + * usually no need to call this method. However, if you take the tasks manually using {@link #takeTask()} or + * {@link #pollTask()}, you have to call this method at the end of task execution loop for accurate quiet period + * checks. + */ + protected void updateLastExecutionTime() { + lastExecutionTime = ScheduledFutureTask.nanoTime(); + } + + /** + * + */ + protected abstract void run(); + + /** + * Do nothing, sub-classes may override + */ + protected void cleanup() { + // NOOP + } + + protected void wakeup(boolean inEventLoop) { + if (!inEventLoop || STATE_UPDATER.get(this) == ST_SHUTTING_DOWN) { + taskQueue.add(WAKEUP_TASK); + } + } + + @Override + public boolean inEventLoop(Thread thread) { + return thread == this.thread; + } + + /** + * Add a {@link Runnable} which will be executed on shutdown of this instance + */ + public void addShutdownHook(final Runnable task) { + if (inEventLoop()) { + shutdownHooks.add(task); + } else { + execute(new Runnable() { + @Override + public void run() { + shutdownHooks.add(task); + } + }); + } + } + + /** + * Remove a previous added {@link Runnable} as a shutdown hook + */ + public void removeShutdownHook(final Runnable task) { + if (inEventLoop()) { + shutdownHooks.remove(task); + } else { + execute(new Runnable() { + @Override + public void run() { + shutdownHooks.remove(task); + } + }); + } + } + + private boolean runShutdownHooks() { + boolean ran = false; + // Note shutdown hooks can add / remove shutdown hooks. + while (!shutdownHooks.isEmpty()) { + List copy = new ArrayList(shutdownHooks); + shutdownHooks.clear(); + for (Runnable task: copy) { + try { + task.run(); + } catch (Throwable t) { + logger.warn("Shutdown hook raised an exception.", t); + } finally { + ran = true; + } + } + } + + if (ran) { + lastExecutionTime = ScheduledFutureTask.nanoTime(); + } + + return ran; + } + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + if (quietPeriod < 0) { + throw new IllegalArgumentException("quietPeriod: " + quietPeriod + " (expected >= 0)"); + } + if (timeout < quietPeriod) { + throw new IllegalArgumentException( + "timeout: " + timeout + " (expected >= quietPeriod (" + quietPeriod + "))"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + + if (isShuttingDown()) { + return terminationFuture(); + } + + boolean inEventLoop = inEventLoop(); + boolean wakeup; + int oldState; + for (;;) { + if (isShuttingDown()) { + return terminationFuture(); + } + int newState; + wakeup = true; + oldState = STATE_UPDATER.get(this); + if (inEventLoop) { + newState = ST_SHUTTING_DOWN; + } else { + switch (oldState) { + case ST_NOT_STARTED: + case ST_STARTED: + newState = ST_SHUTTING_DOWN; + break; + default: + newState = oldState; + wakeup = false; + } + } + if (STATE_UPDATER.compareAndSet(this, oldState, newState)) { + break; + } + } + gracefulShutdownQuietPeriod = unit.toNanos(quietPeriod); + gracefulShutdownTimeout = unit.toNanos(timeout); + + if (oldState == ST_NOT_STARTED) { + thread.start(); + } + + if (wakeup) { + wakeup(inEventLoop); + } + + return terminationFuture(); + } + + @Override + public Future terminationFuture() { + return terminationFuture; + } + + @Override + @Deprecated + public void shutdown() { + if (isShutdown()) { + return; + } + + boolean inEventLoop = inEventLoop(); + boolean wakeup; + int oldState; + for (;;) { + if (isShuttingDown()) { + return; + } + int newState; + wakeup = true; + oldState = STATE_UPDATER.get(this); + if (inEventLoop) { + newState = ST_SHUTDOWN; + } else { + switch (oldState) { + case ST_NOT_STARTED: + case ST_STARTED: + case ST_SHUTTING_DOWN: + newState = ST_SHUTDOWN; + break; + default: + newState = oldState; + wakeup = false; + } + } + if (STATE_UPDATER.compareAndSet(this, oldState, newState)) { + break; + } + } + + if (oldState == ST_NOT_STARTED) { + thread.start(); + } + + if (wakeup) { + wakeup(inEventLoop); + } + } + + @Override + public boolean isShuttingDown() { + return STATE_UPDATER.get(this) >= ST_SHUTTING_DOWN; + } + + @Override + public boolean isShutdown() { + return STATE_UPDATER.get(this) >= ST_SHUTDOWN; + } + + @Override + public boolean isTerminated() { + return STATE_UPDATER.get(this) == ST_TERMINATED; + } + + /** + * Confirm that the shutdown if the instance should be done now! + */ + protected boolean confirmShutdown() { + if (!isShuttingDown()) { + return false; + } + + if (!inEventLoop()) { + throw new IllegalStateException("must be invoked from an event loop"); + } + + cancelDelayedTasks(); + + if (gracefulShutdownStartTime == 0) { + gracefulShutdownStartTime = ScheduledFutureTask.nanoTime(); + } + + if (runAllTasks() || runShutdownHooks()) { + if (isShutdown()) { + // Executor shut down - no new tasks anymore. + return true; + } + + // There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period. + wakeup(true); + return false; + } + + final long nanoTime = ScheduledFutureTask.nanoTime(); + + if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) { + return true; + } + + if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) { + // Check if any tasks were added to the queue every 100ms. + // TODO: Change the behavior of takeTask() so that it returns on timeout. + wakeup(true); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore + } + + return false; + } + + // No tasks were added for last quiet period - hopefully safe to shut down. + // (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.) + return true; + } + + private void cancelDelayedTasks() { + if (delayedTaskQueue.isEmpty()) { + return; + } + + final ScheduledFutureTask[] delayedTasks = + delayedTaskQueue.toArray(new ScheduledFutureTask[delayedTaskQueue.size()]); + + for (ScheduledFutureTask task: delayedTasks) { + task.cancel(false); + } + + delayedTaskQueue.clear(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + if (unit == null) { + throw new NullPointerException("unit"); + } + + if (inEventLoop()) { + throw new IllegalStateException("cannot await termination of the current thread"); + } + + if (threadLock.tryAcquire(timeout, unit)) { + threadLock.release(); + } + + return isTerminated(); + } + + @Override + public void execute(Runnable task) { + if (task == null) { + throw new NullPointerException("task"); + } + + boolean inEventLoop = inEventLoop(); + if (inEventLoop) { + addTask(task); + } else { + startThread(); + addTask(task); + if (isShutdown() && removeTask(task)) { + reject(); + } + } + + if (!addTaskWakesUp && wakesUpForTask(task)) { + wakeup(inEventLoop); + } + } + + + protected boolean wakesUpForTask(Runnable task) { + return true; + } + + protected static void reject() { + throw new RejectedExecutionException("event executor terminated"); + } + + // ScheduledExecutorService implementation + + private static final long SCHEDULE_PURGE_INTERVAL = TimeUnit.SECONDS.toNanos(1); + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + if (command == null) { + throw new NullPointerException("command"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (delay < 0) { + throw new IllegalArgumentException( + String.format("delay: %d (expected: >= 0)", delay)); + } + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + if (callable == null) { + throw new NullPointerException("callable"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (delay < 0) { + throw new IllegalArgumentException( + String.format("delay: %d (expected: >= 0)", delay)); + } + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, callable, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + if (command == null) { + throw new NullPointerException("command"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (initialDelay < 0) { + throw new IllegalArgumentException( + String.format("initialDelay: %d (expected: >= 0)", initialDelay)); + } + if (period <= 0) { + throw new IllegalArgumentException( + String.format("period: %d (expected: > 0)", period)); + } + + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, Executors.callable(command, null), + ScheduledFutureTask.deadlineNanos(unit.toNanos(initialDelay)), unit.toNanos(period))); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + if (command == null) { + throw new NullPointerException("command"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (initialDelay < 0) { + throw new IllegalArgumentException( + String.format("initialDelay: %d (expected: >= 0)", initialDelay)); + } + if (delay <= 0) { + throw new IllegalArgumentException( + String.format("delay: %d (expected: > 0)", delay)); + } + + return schedule(new ScheduledFutureTask( + this, delayedTaskQueue, Executors.callable(command, null), + ScheduledFutureTask.deadlineNanos(unit.toNanos(initialDelay)), -unit.toNanos(delay))); + } + + private ScheduledFuture schedule(final ScheduledFutureTask task) { + if (task == null) { + throw new NullPointerException("task"); + } + + if (inEventLoop()) { + delayedTaskQueue.add(task); + } else { + execute(new Runnable() { + @Override + public void run() { + delayedTaskQueue.add(task); + } + }); + } + + return task; + } + + private void startThread() { + if (STATE_UPDATER.get(this) == ST_NOT_STARTED) { + if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) { + delayedTaskQueue.add(new ScheduledFutureTask( + this, delayedTaskQueue, Executors.callable(new PurgeTask(), null), + ScheduledFutureTask.deadlineNanos(SCHEDULE_PURGE_INTERVAL), -SCHEDULE_PURGE_INTERVAL)); + thread.start(); + } + } + } + + private final class PurgeTask implements Runnable { + @Override + public void run() { + Iterator> i = delayedTaskQueue.iterator(); + while (i.hasNext()) { + ScheduledFutureTask task = i.next(); + if (task.isCancelled()) { + i.remove(); + } + } + } + } +} diff --git a/common/src/common/net/util/concurrent/SucceededFuture.java b/common/src/common/net/util/concurrent/SucceededFuture.java new file mode 100644 index 0000000..ddcd86f --- /dev/null +++ b/common/src/common/net/util/concurrent/SucceededFuture.java @@ -0,0 +1,50 @@ +/* + * 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 common.net.util.concurrent; + +/** + * The {@link CompleteFuture} which is succeeded already. It is + * recommended to use {@link EventExecutor#newSucceededFuture(Object)} instead of + * calling the constructor of this future. + */ +public final class SucceededFuture extends CompleteFuture { + private final V result; + + /** + * Creates a new instance. + * + * @param executor the {@link EventExecutor} associated with this future + */ + public SucceededFuture(EventExecutor executor, V result) { + super(executor); + this.result = result; + } + + @Override + public Throwable cause() { + return null; + } + + @Override + public boolean isSuccess() { + return true; + } + + @Override + public V getNow() { + return result; + } +} diff --git a/common/src/common/net/util/internal/Cleaner0.java b/common/src/common/net/util/internal/Cleaner0.java new file mode 100644 index 0000000..ceb3aeb --- /dev/null +++ b/common/src/common/net/util/internal/Cleaner0.java @@ -0,0 +1,74 @@ +/* +* Copyright 2014 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 common.net.util.internal; + +import java.lang.reflect.Field; +import java.nio.ByteBuffer; + +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; +import sun.misc.Cleaner; + + +/** + * Allows to free direct {@link ByteBuffer} by using {@link Cleaner}. This is encapsulated in an extra class to be able + * to use {@link PlatformDependent0} on Android without problems. + * + * For more details see #2604. + */ +final class Cleaner0 { + private static final long CLEANER_FIELD_OFFSET; + private static final InternalLogger logger = InternalLoggerFactory.getInstance(Cleaner0.class); + + static { + ByteBuffer direct = ByteBuffer.allocateDirect(1); + Field cleanerField; + long fieldOffset = -1; + if (PlatformDependent0.hasUnsafe()) { + try { + cleanerField = direct.getClass().getDeclaredField("cleaner"); + cleanerField.setAccessible(true); + Cleaner cleaner = (Cleaner) cleanerField.get(direct); + cleaner.clean(); + fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField); + } catch (Throwable t) { + // We don't have ByteBuffer.cleaner(). + fieldOffset = -1; + } + } + logger.debug("java.nio.ByteBuffer.cleaner(): {}", fieldOffset != -1? "available" : "unavailable"); + CLEANER_FIELD_OFFSET = fieldOffset; + + // free buffer if possible + freeDirectBuffer(direct); + } + + static void freeDirectBuffer(ByteBuffer buffer) { + if (CLEANER_FIELD_OFFSET == -1 || !buffer.isDirect()) { + return; + } + try { + Cleaner cleaner = (Cleaner) PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET); + if (cleaner != null) { + cleaner.clean(); + } + } catch (Throwable t) { + // Nothing we can do here. + } + } + + private Cleaner0() { } +} diff --git a/common/src/common/net/util/internal/EmptyArrays.java b/common/src/common/net/util/internal/EmptyArrays.java new file mode 100644 index 0000000..e5fac07 --- /dev/null +++ b/common/src/common/net/util/internal/EmptyArrays.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 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 common.net.util.internal; + +import java.nio.ByteBuffer; +import java.security.cert.X509Certificate; + +public final class EmptyArrays { + + public static final byte[] EMPTY_BYTES = new byte[0]; + public static final boolean[] EMPTY_BOOLEANS = new boolean[0]; + public static final double[] EMPTY_DOUBLES = new double[0]; + public static final float[] EMPTY_FLOATS = new float[0]; + public static final int[] EMPTY_INTS = new int[0]; + public static final short[] EMPTY_SHORTS = new short[0]; + public static final long[] EMPTY_LONGS = new long[0]; + public static final Object[] EMPTY_OBJECTS = new Object[0]; + public static final Class[] EMPTY_CLASSES = new Class[0]; + public static final String[] EMPTY_STRINGS = new String[0]; + public static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; + public static final ByteBuffer[] EMPTY_BYTE_BUFFERS = new ByteBuffer[0]; + public static final X509Certificate[] EMPTY_X509_CERTIFICATES = new X509Certificate[0]; + + private EmptyArrays() { } +} diff --git a/common/src/common/net/util/internal/IntegerHolder.java b/common/src/common/net/util/internal/IntegerHolder.java new file mode 100644 index 0000000..992a27c --- /dev/null +++ b/common/src/common/net/util/internal/IntegerHolder.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +public final class IntegerHolder { + public int value; +} diff --git a/common/src/common/net/util/internal/InternalThreadLocalMap.java b/common/src/common/net/util/internal/InternalThreadLocalMap.java new file mode 100644 index 0000000..85565f0 --- /dev/null +++ b/common/src/common/net/util/internal/InternalThreadLocalMap.java @@ -0,0 +1,309 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.WeakHashMap; + +import common.net.util.concurrent.FastThreadLocalThread; + +/** + * The internal data structure that stores the thread-local variables for Netty and all {@link FastThreadLocal}s. + * Note that this class is for internal use only and is subject to change at any time. Use {@link FastThreadLocal} + * unless you know what you are doing. + */ +public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap { + + public static final Object UNSET = new Object(); + + public static InternalThreadLocalMap getIfSet() { + Thread thread = Thread.currentThread(); + InternalThreadLocalMap threadLocalMap; + if (thread instanceof FastThreadLocalThread) { + threadLocalMap = ((FastThreadLocalThread) thread).threadLocalMap(); + } else { + ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; + if (slowThreadLocalMap == null) { + threadLocalMap = null; + } else { + threadLocalMap = slowThreadLocalMap.get(); + } + } + return threadLocalMap; + } + + public static InternalThreadLocalMap get() { + Thread thread = Thread.currentThread(); + if (thread instanceof FastThreadLocalThread) { + return fastGet((FastThreadLocalThread) thread); + } else { + return slowGet(); + } + } + + private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { + InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); + if (threadLocalMap == null) { + thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap()); + } + return threadLocalMap; + } + + private static InternalThreadLocalMap slowGet() { + ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; + if (slowThreadLocalMap == null) { + UnpaddedInternalThreadLocalMap.slowThreadLocalMap = + slowThreadLocalMap = new ThreadLocal(); + } + + InternalThreadLocalMap ret = slowThreadLocalMap.get(); + if (ret == null) { + ret = new InternalThreadLocalMap(); + slowThreadLocalMap.set(ret); + } + return ret; + } + + public static void remove() { + Thread thread = Thread.currentThread(); + if (thread instanceof FastThreadLocalThread) { + ((FastThreadLocalThread) thread).setThreadLocalMap(null); + } else { + ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; + if (slowThreadLocalMap != null) { + slowThreadLocalMap.remove(); + } + } + } + + public static void destroy() { + slowThreadLocalMap = null; + } + + public static int nextVariableIndex() { + int index = nextIndex.getAndIncrement(); + if (index < 0) { + nextIndex.decrementAndGet(); + throw new IllegalStateException("too many thread-local indexed variables"); + } + return index; + } + + public static int lastVariableIndex() { + return nextIndex.get() - 1; + } + + // Cache line padding (must be public) + // With CompressedOops enabled, an instance of this class should occupy at least 128 bytes. + public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9; + + private InternalThreadLocalMap() { + super(newIndexedVariableTable()); + } + + private static Object[] newIndexedVariableTable() { + Object[] array = new Object[32]; + Arrays.fill(array, UNSET); + return array; + } + + public int size() { + int count = 0; + + if (futureListenerStackDepth != 0) { + count ++; + } + if (localChannelReaderStackDepth != 0) { + count ++; + } + if (handlerSharableCache != null) { + count ++; + } + if (counterHashCode != null) { + count ++; + } + if (random != null) { + count ++; + } + if (typeParameterMatcherGetCache != null) { + count ++; + } + if (typeParameterMatcherFindCache != null) { + count ++; + } + if (stringBuilder != null) { + count ++; + } + if (charsetEncoderCache != null) { + count ++; + } + if (charsetDecoderCache != null) { + count ++; + } + + for (Object o: indexedVariables) { + if (o != UNSET) { + count ++; + } + } + + // We should subtract 1 from the count because the first element in 'indexedVariables' is reserved + // by 'FastThreadLocal' to keep the list of 'FastThreadLocal's to remove on 'FastThreadLocal.removeAll()'. + return count - 1; + } + + public StringBuilder stringBuilder() { + StringBuilder builder = stringBuilder; + if (builder == null) { + stringBuilder = builder = new StringBuilder(512); + } else { + builder.setLength(0); + } + return builder; + } + + public Map charsetEncoderCache() { + Map cache = charsetEncoderCache; + if (cache == null) { + charsetEncoderCache = cache = new IdentityHashMap(); + } + return cache; + } + + public Map charsetDecoderCache() { + Map cache = charsetDecoderCache; + if (cache == null) { + charsetDecoderCache = cache = new IdentityHashMap(); + } + return cache; + } + + public int futureListenerStackDepth() { + return futureListenerStackDepth; + } + + public void setFutureListenerStackDepth(int futureListenerStackDepth) { + this.futureListenerStackDepth = futureListenerStackDepth; + } + + public ThreadLocalRandom random() { + ThreadLocalRandom r = random; + if (r == null) { + random = r = new ThreadLocalRandom(); + } + return r; + } + + public Map, TypeParameterMatcher> typeParameterMatcherGetCache() { + Map, TypeParameterMatcher> cache = typeParameterMatcherGetCache; + if (cache == null) { + typeParameterMatcherGetCache = cache = new IdentityHashMap, TypeParameterMatcher>(); + } + return cache; + } + + public Map, Map> typeParameterMatcherFindCache() { + Map, Map> cache = typeParameterMatcherFindCache; + if (cache == null) { + typeParameterMatcherFindCache = cache = new IdentityHashMap, Map>(); + } + return cache; + } + + public IntegerHolder counterHashCode() { + return counterHashCode; + } + + public void setCounterHashCode(IntegerHolder counterHashCode) { + this.counterHashCode = counterHashCode; + } + + public Map, Boolean> handlerSharableCache() { + Map, Boolean> cache = handlerSharableCache; + if (cache == null) { + // Start with small capacity to keep memory overhead as low as possible. + handlerSharableCache = cache = new WeakHashMap, Boolean>(4); + } + return cache; + } + + public int localChannelReaderStackDepth() { + return localChannelReaderStackDepth; + } + + public void setLocalChannelReaderStackDepth(int localChannelReaderStackDepth) { + this.localChannelReaderStackDepth = localChannelReaderStackDepth; + } + + public Object indexedVariable(int index) { + Object[] lookup = indexedVariables; + return index < lookup.length? lookup[index] : UNSET; + } + + /** + * @return {@code true} if and only if a new thread-local variable has been created + */ + public boolean setIndexedVariable(int index, Object value) { + Object[] lookup = indexedVariables; + if (index < lookup.length) { + Object oldValue = lookup[index]; + lookup[index] = value; + return oldValue == UNSET; + } else { + expandIndexedVariableTableAndSet(index, value); + return true; + } + } + + private void expandIndexedVariableTableAndSet(int index, Object value) { + Object[] oldArray = indexedVariables; + final int oldCapacity = oldArray.length; + int newCapacity = index; + newCapacity |= newCapacity >>> 1; + newCapacity |= newCapacity >>> 2; + newCapacity |= newCapacity >>> 4; + newCapacity |= newCapacity >>> 8; + newCapacity |= newCapacity >>> 16; + newCapacity ++; + + Object[] newArray = Arrays.copyOf(oldArray, newCapacity); + Arrays.fill(newArray, oldCapacity, newArray.length, UNSET); + newArray[index] = value; + indexedVariables = newArray; + } + + public Object removeIndexedVariable(int index) { + Object[] lookup = indexedVariables; + if (index < lookup.length) { + Object v = lookup[index]; + lookup[index] = UNSET; + return v; + } else { + return UNSET; + } + } + + public boolean isIndexedVariableSet(int index) { + Object[] lookup = indexedVariables; + return index < lookup.length && lookup[index] != UNSET; + } +} diff --git a/common/src/common/net/util/internal/MpscLinkedQueue.java b/common/src/common/net/util/internal/MpscLinkedQueue.java new file mode 100644 index 0000000..1b32c49 --- /dev/null +++ b/common/src/common/net/util/internal/MpscLinkedQueue.java @@ -0,0 +1,397 @@ +/* + * Copyright 2014 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 (C) 2009-2013 Typesafe Inc. + */ +package common.net.util.internal; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; + +/** + * A lock-free concurrent single-consumer multi-producer {@link Queue}. + * It allows multiple producer threads to perform the following operations simultaneously: + *
    + *
  • {@link #offer(Object)}, {@link #add(Object)}, and {@link #addAll(Collection)}
  • + *
  • All other read-only operations: + *
      + *
    • {@link #contains(Object)} and {@link #containsAll(Collection)}
    • + *
    • {@link #element()}, {@link #peek()}
    • + *
    • {@link #size()} and {@link #isEmpty()}
    • + *
    • {@link #iterator()} (except {@link Iterator#remove()}
    • + *
    • {@link #toArray()} and {@link #toArray(Object[])}
    • + *
    + *
  • + *
+ * .. while only one consumer thread is allowed to perform the following operations exclusively: + *
    + *
  • {@link #poll()} and {@link #remove()}
  • + *
  • {@link #remove(Object)}, {@link #removeAll(Collection)}, and {@link #retainAll(Collection)}
  • + *
  • {@link #clear()}
  • {@link #} + *
+ * + * The behavior of this implementation is undefined if you perform the operations for a consumer thread only + * from multiple threads. + * + * The initial implementation is based on: + * + * and adopted padded head node changes from: + * + * data structure modified to avoid false sharing between head and tail Ref as per implementation of MpscLinkedQueue + * on JCTools project. + */ +final class MpscLinkedQueue extends MpscLinkedQueueTailRef implements Queue { + + private static final long serialVersionUID = -1878402552271506449L; + + long p00, p01, p02, p03, p04, p05, p06, p07; + long p30, p31, p32, p33, p34, p35, p36, p37; + + // offer() occurs at the tail of the linked list. + // poll() occurs at the head of the linked list. + // + // Resulting layout is: + // + // head --next--> 1st element --next--> 2nd element --next--> ... tail (last element) + // + // where the head is a dummy node whose value is null. + // + // offer() appends a new node next to the tail using AtomicReference.getAndSet() + // poll() removes head from the linked list and promotes the 1st element to the head, + // setting its value to null if possible. + // + // Also note that this class extends AtomicReference for the "tail" slot (which is the one that is appended to) + // since Unsafe does not expose XCHG operation intrinsically. + MpscLinkedQueue() { + MpscLinkedQueueNode tombstone = new DefaultNode(null); + setHeadRef(tombstone); + setTailRef(tombstone); + } + + /** + * Returns the node right next to the head, which contains the first element of this queue. + */ + private MpscLinkedQueueNode peekNode() { + for (;;) { + final MpscLinkedQueueNode head = headRef(); + final MpscLinkedQueueNode next = head.next(); + if (next != null) { + return next; + } + if (head == tailRef()) { + return null; + } + + // If we are here, it means: + // * offer() is adding the first element, and + // * it's between replaceTail(newTail) and oldTail.setNext(newTail). + // (i.e. next == oldTail and oldTail.next == null and head == oldTail != newTail) + } + } + + @Override + + public boolean offer(E value) { + if (value == null) { + throw new NullPointerException("value"); + } + + final MpscLinkedQueueNode newTail; + if (value instanceof MpscLinkedQueueNode) { + newTail = (MpscLinkedQueueNode) value; + newTail.setNext(null); + } else { + newTail = new DefaultNode(value); + } + + MpscLinkedQueueNode oldTail = getAndSetTailRef(newTail); + oldTail.setNext(newTail); + return true; + } + + @Override + public E poll() { + final MpscLinkedQueueNode next = peekNode(); + if (next == null) { + return null; + } + + // next becomes a new head. + MpscLinkedQueueNode oldHead = headRef(); + // Similar to 'headRef.node = next', but slightly faster (storestore vs loadstore) + // See: http://robsjava.blogspot.com/2013/06/a-faster-volatile.html + // See: http://psy-lob-saw.blogspot.com/2012/12/atomiclazyset-is-performance-win-for.html + lazySetHeadRef(next); + + // Break the linkage between the old head and the new head. + oldHead.unlink(); + + return next.clearMaybe(); + } + + @Override + public E peek() { + final MpscLinkedQueueNode next = peekNode(); + if (next == null) { + return null; + } + return next.value(); + } + + @Override + public int size() { + int count = 0; + MpscLinkedQueueNode n = peekNode(); + for (;;) { + if (n == null) { + break; + } + count ++; + n = n.next(); + } + return count; + } + + @Override + public boolean isEmpty() { + return peekNode() == null; + } + + @Override + public boolean contains(Object o) { + MpscLinkedQueueNode n = peekNode(); + for (;;) { + if (n == null) { + break; + } + if (n.value() == o) { + return true; + } + n = n.next(); + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private MpscLinkedQueueNode node = peekNode(); + + @Override + public boolean hasNext() { + return node != null; + } + + @Override + public E next() { + MpscLinkedQueueNode node = this.node; + if (node == null) { + throw new NoSuchElementException(); + } + E value = node.value(); + this.node = node.next(); + return value; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public boolean add(E e) { + if (offer(e)) { + return true; + } + throw new IllegalStateException("queue full"); + } + + @Override + public E remove() { + E e = poll(); + if (e != null) { + return e; + } + throw new NoSuchElementException(); + } + + @Override + public E element() { + E e = peek(); + if (e != null) { + return e; + } + throw new NoSuchElementException(); + } + + @Override + public Object[] toArray() { + final Object[] array = new Object[size()]; + final Iterator it = iterator(); + for (int i = 0; i < array.length; i ++) { + if (it.hasNext()) { + array[i] = it.next(); + } else { + return Arrays.copyOf(array, i); + } + } + return array; + } + + @Override + + public T[] toArray(T[] a) { + final int size = size(); + final T[] array; + if (a.length >= size) { + array = a; + } else { + array = (T[]) Array.newInstance(a.getClass().getComponentType(), size); + } + + final Iterator it = iterator(); + for (int i = 0; i < array.length; i++) { + if (it.hasNext()) { + array[i] = (T) it.next(); + } else { + if (a == array) { + array[i] = null; + return array; + } + + if (a.length < i) { + return Arrays.copyOf(array, i); + } + + System.arraycopy(array, 0, a, 0, i); + if (a.length > i) { + a[i] = null; + } + return a; + } + } + return array; + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + for (Object e: c) { + if (!contains(e)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + if (c == null) { + throw new NullPointerException("c"); + } + if (c == this) { + throw new IllegalArgumentException("c == this"); + } + + boolean modified = false; + for (E e: c) { + add(e); + modified = true; + } + return modified; + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + while (poll() != null) { + continue; + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + for (E e: this) { + out.writeObject(e); + } + out.writeObject(null); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + + final MpscLinkedQueueNode tombstone = new DefaultNode(null); + setHeadRef(tombstone); + setTailRef(tombstone); + + for (;;) { + + E e = (E) in.readObject(); + if (e == null) { + break; + } + add(e); + } + } + + private static final class DefaultNode extends MpscLinkedQueueNode { + + private T value; + + DefaultNode(T value) { + this.value = value; + } + + @Override + public T value() { + return value; + } + + @Override + protected T clearMaybe() { + T value = this.value; + this.value = null; + return value; + } + } +} diff --git a/common/src/common/net/util/internal/MpscLinkedQueueHeadRef.java b/common/src/common/net/util/internal/MpscLinkedQueueHeadRef.java new file mode 100644 index 0000000..9b4d089 --- /dev/null +++ b/common/src/common/net/util/internal/MpscLinkedQueueHeadRef.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + + +abstract class MpscLinkedQueueHeadRef extends MpscLinkedQueuePad0 implements Serializable { + + private static final long serialVersionUID = 8467054865577874285L; + + + private static final AtomicReferenceFieldUpdater UPDATER; + + static { + + AtomicReferenceFieldUpdater updater; + updater = PlatformDependent.newAtomicReferenceFieldUpdater(MpscLinkedQueueHeadRef.class, "headRef"); + if (updater == null) { + updater = AtomicReferenceFieldUpdater.newUpdater( + MpscLinkedQueueHeadRef.class, MpscLinkedQueueNode.class, "headRef"); + } + UPDATER = updater; + } + + private transient volatile MpscLinkedQueueNode headRef; + + protected final MpscLinkedQueueNode headRef() { + return headRef; + } + + protected final void setHeadRef(MpscLinkedQueueNode headRef) { + this.headRef = headRef; + } + + protected final void lazySetHeadRef(MpscLinkedQueueNode headRef) { + UPDATER.lazySet(this, headRef); + } +} diff --git a/common/src/common/net/util/internal/MpscLinkedQueueNode.java b/common/src/common/net/util/internal/MpscLinkedQueueNode.java new file mode 100644 index 0000000..1c3f007 --- /dev/null +++ b/common/src/common/net/util/internal/MpscLinkedQueueNode.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +public abstract class MpscLinkedQueueNode { + + + private static final AtomicReferenceFieldUpdater nextUpdater; + + static { + + AtomicReferenceFieldUpdater u; + + u = PlatformDependent.newAtomicReferenceFieldUpdater(MpscLinkedQueueNode.class, "next"); + if (u == null) { + u = AtomicReferenceFieldUpdater.newUpdater(MpscLinkedQueueNode.class, MpscLinkedQueueNode.class, "next"); + } + nextUpdater = u; + } + + + private volatile MpscLinkedQueueNode next; + + final MpscLinkedQueueNode next() { + return next; + } + + final void setNext(final MpscLinkedQueueNode newNext) { + // Similar to 'next = newNext', but slightly faster (storestore vs loadstore) + // See: http://robsjava.blogspot.com/2013/06/a-faster-volatile.html + nextUpdater.lazySet(this, newNext); + } + + public abstract T value(); + + /** + * Sets the element this node contains to {@code null} so that the node can be used as a tombstone. + */ + protected T clearMaybe() { + return value(); + } + + /** + * Unlink to allow GC'ed + */ + void unlink() { + setNext(null); + } +} diff --git a/common/src/common/net/util/internal/MpscLinkedQueuePad0.java b/common/src/common/net/util/internal/MpscLinkedQueuePad0.java new file mode 100644 index 0000000..0fe9e45 --- /dev/null +++ b/common/src/common/net/util/internal/MpscLinkedQueuePad0.java @@ -0,0 +1,22 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +abstract class MpscLinkedQueuePad0 { + long p00, p01, p02, p03, p04, p05, p06, p07; + long p30, p31, p32, p33, p34, p35, p36, p37; +} diff --git a/common/src/common/net/util/internal/MpscLinkedQueuePad1.java b/common/src/common/net/util/internal/MpscLinkedQueuePad1.java new file mode 100644 index 0000000..6cfd53f --- /dev/null +++ b/common/src/common/net/util/internal/MpscLinkedQueuePad1.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +abstract class MpscLinkedQueuePad1 extends MpscLinkedQueueHeadRef { + + private static final long serialVersionUID = 2886694927079691637L; + + long p00, p01, p02, p03, p04, p05, p06, p07; + long p30, p31, p32, p33, p34, p35, p36, p37; +} diff --git a/common/src/common/net/util/internal/MpscLinkedQueueTailRef.java b/common/src/common/net/util/internal/MpscLinkedQueueTailRef.java new file mode 100644 index 0000000..fa2a36c --- /dev/null +++ b/common/src/common/net/util/internal/MpscLinkedQueueTailRef.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +abstract class MpscLinkedQueueTailRef extends MpscLinkedQueuePad1 { + + private static final long serialVersionUID = 8717072462993327429L; + + + private static final AtomicReferenceFieldUpdater UPDATER; + + static { + + AtomicReferenceFieldUpdater updater; + updater = PlatformDependent.newAtomicReferenceFieldUpdater(MpscLinkedQueueTailRef.class, "tailRef"); + if (updater == null) { + updater = AtomicReferenceFieldUpdater.newUpdater( + MpscLinkedQueueTailRef.class, MpscLinkedQueueNode.class, "tailRef"); + } + UPDATER = updater; + } + + private transient volatile MpscLinkedQueueNode tailRef; + + protected final MpscLinkedQueueNode tailRef() { + return tailRef; + } + + protected final void setTailRef(MpscLinkedQueueNode tailRef) { + this.tailRef = tailRef; + } + + + protected final MpscLinkedQueueNode getAndSetTailRef(MpscLinkedQueueNode tailRef) { + // LOCK XCHG in JDK8, a CAS loop in JDK 7/6 + return (MpscLinkedQueueNode) UPDATER.getAndSet(this, tailRef); + } +} diff --git a/common/src/common/net/util/internal/NoOpTypeParameterMatcher.java b/common/src/common/net/util/internal/NoOpTypeParameterMatcher.java new file mode 100644 index 0000000..fe4d34f --- /dev/null +++ b/common/src/common/net/util/internal/NoOpTypeParameterMatcher.java @@ -0,0 +1,24 @@ +/* + * Copyright 2013 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 common.net.util.internal; + +public final class NoOpTypeParameterMatcher extends TypeParameterMatcher { + @Override + public boolean match(Object msg) { + return true; + } +} diff --git a/common/src/common/net/util/internal/OneTimeTask.java b/common/src/common/net/util/internal/OneTimeTask.java new file mode 100644 index 0000000..7fdb8ae --- /dev/null +++ b/common/src/common/net/util/internal/OneTimeTask.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +/** + * {@link Runnable} which represent a one time task which may allow the {@link EventExecutor} to reduce the amount of + * produced garbage when queue it for execution. + * + * It is important this will not be reused. After submitted it is not allowed to get submitted again! + */ +public abstract class OneTimeTask extends MpscLinkedQueueNode implements Runnable { + + @Override + public Runnable value() { + return this; + } +} diff --git a/common/src/common/net/util/internal/PlatformDependent.java b/common/src/common/net/util/internal/PlatformDependent.java new file mode 100644 index 0000000..70db87e --- /dev/null +++ b/common/src/common/net/util/internal/PlatformDependent.java @@ -0,0 +1,844 @@ +/* + * 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 common.net.util.internal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import common.net.util.CharsetUtil; +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + + +/** + * Utility that detects various properties specific to the current runtime + * environment, such as Java version and the availability of the + * {@code sun.misc.Unsafe} object. + *

+ * You can disable the use of {@code sun.misc.Unsafe} if you specify + * the system property game.net.noUnsafe. + */ +public final class PlatformDependent { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent.class); + + private static final Pattern MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN = Pattern.compile( + "\\s*-XX:MaxDirectMemorySize\\s*=\\s*([0-9]+)\\s*([kKmMgG]?)\\s*$"); + + private static final boolean IS_ANDROID = isAndroid0(); + private static final boolean IS_WINDOWS = isWindows0(); + private static final boolean IS_ROOT = isRoot0(); + + private static final int JAVA_VERSION = javaVersion0(); + + private static final boolean CAN_ENABLE_TCP_NODELAY_BY_DEFAULT = !isAndroid(); + + private static final boolean HAS_UNSAFE = hasUnsafe0(); +// private static final boolean CAN_USE_CHM_V8 = HAS_UNSAFE && JAVA_VERSION < 8; + private static final boolean DIRECT_BUFFER_PREFERRED = + HAS_UNSAFE && !SystemPropertyUtil.getBoolean("game.net.noPreferDirect", false); + private static final long MAX_DIRECT_MEMORY = maxDirectMemory0(); + + private static final long ARRAY_BASE_OFFSET = arrayBaseOffset0(); + +// private static final boolean HAS_JAVASSIST = hasJavassist0(); + +// private static final File TMPDIR = tmpdir0(); +// +// private static final int BIT_MODE = bitMode0(); +// +// private static final int ADDRESS_SIZE = addressSize0(); + + static { + if (logger.isDebugEnabled()) { + logger.debug("-Dgame.net.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED); + } + + if (!hasUnsafe() && !isAndroid()) { + logger.info( + "Your platform does not provide complete low-level API for accessing direct buffers reliably. " + + "Unless explicitly requested, heap buffer will always be preferred to avoid potential system " + + "unstability."); + } + } + + /** + * Returns {@code true} if and only if the current platform is Android + */ + public static boolean isAndroid() { + return IS_ANDROID; + } + + /** + * Return {@code true} if the JVM is running on Windows + */ + public static boolean isWindows() { + return IS_WINDOWS; + } + + /** + * Return {@code true} if the current user is root. Note that this method returns + * {@code false} if on Windows. + */ + public static boolean isRoot() { + return IS_ROOT; + } + + /** + * Return the version of Java under which this library is used. + */ + public static int javaVersion() { + return JAVA_VERSION; + } + + /** + * Returns {@code true} if and only if it is fine to enable TCP_NODELAY socket option by default. + */ + public static boolean canEnableTcpNoDelayByDefault() { + return CAN_ENABLE_TCP_NODELAY_BY_DEFAULT; + } + + /** + * Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used for acclerated + * direct memory access. + */ + public static boolean hasUnsafe() { + return HAS_UNSAFE; + } + + /** + * Returns {@code true} if the platform has reliable low-level direct buffer access API and a user specified + * {@code -Dgame.net.preferDirect} option. + */ + public static boolean directBufferPreferred() { + return DIRECT_BUFFER_PREFERRED; + } + + /** + * Returns the maximum memory reserved for direct buffer allocation. + */ + public static long maxDirectMemory() { + return MAX_DIRECT_MEMORY; + } + + /** + * Returns {@code true} if and only if Javassist is available. + */ +// public static boolean hasJavassist() { +// return HAS_JAVASSIST; +// } + + /** + * Returns the temporary directory. + */ +// public static File tmpdir() { +// return TMPDIR; +// } + + /** + * Returns the bit mode of the current VM (usually 32 or 64.) + */ +// public static int bitMode() { +// return BIT_MODE; +// } + + /** + * Return the address size of the OS. + * 4 (for 32 bits systems ) and 8 (for 64 bits systems). + */ +// public static int addressSize() { +// return ADDRESS_SIZE; +// } + +// public static long allocateMemory(long size) { +// return PlatformDependent0.allocateMemory(size); +// } + +// public static void freeMemory(long address) { +// PlatformDependent0.freeMemory(address); +// } + + /** + * Raises an exception bypassing compiler checks for checked exceptions. + */ + public static void throwException(Throwable t) { + if (hasUnsafe()) { + PlatformDependent0.throwException(t); + } else { + PlatformDependent.throwException0(t); + } + } + + + private static void throwException0(Throwable t) throws E { + throw (E) t; + } + + /** + * Creates a new fastest {@link ConcurrentMap} implementaion for the current platform. + */ + public static ConcurrentMap newConcurrentHashMap() { +// if (CAN_USE_CHM_V8) { +// return new ConcurrentHashMapV8(); +// } else { + return new ConcurrentHashMap(); +// } + } + + /** + * Creates a new fastest {@link ConcurrentMap} implementaion for the current platform. + */ + public static ConcurrentMap newConcurrentHashMap(int initialCapacity) { +// if (CAN_USE_CHM_V8) { +// return new ConcurrentHashMapV8(initialCapacity); +// } else { + return new ConcurrentHashMap(initialCapacity); +// } + } + + /** + * Creates a new fastest {@link ConcurrentMap} implementaion for the current platform. + */ + public static ConcurrentMap newConcurrentHashMap(int initialCapacity, float loadFactor) { +// if (CAN_USE_CHM_V8) { +// return new ConcurrentHashMapV8(initialCapacity, loadFactor); +// } else { + return new ConcurrentHashMap(initialCapacity, loadFactor); +// } + } + + /** + * Creates a new fastest {@link ConcurrentMap} implementaion for the current platform. + */ + public static ConcurrentMap newConcurrentHashMap( + int initialCapacity, float loadFactor, int concurrencyLevel) { +// if (CAN_USE_CHM_V8) { +// return new ConcurrentHashMapV8(initialCapacity, loadFactor, concurrencyLevel); +// } else { + return new ConcurrentHashMap(initialCapacity, loadFactor, concurrencyLevel); +// } + } + + /** + * Creates a new fastest {@link ConcurrentMap} implementaion for the current platform. + */ + public static ConcurrentMap newConcurrentHashMap(Map map) { +// if (CAN_USE_CHM_V8) { +// return new ConcurrentHashMapV8(map); +// } else { + return new ConcurrentHashMap(map); +// } + } + + /** + * Try to deallocate the specified direct {@link ByteBuffer}. Please note this method does nothing if + * the current platform does not support this operation or the specified buffer is not a direct buffer. + */ + public static void freeDirectBuffer(ByteBuffer buffer) { + if (hasUnsafe() && !isAndroid()) { + // only direct to method if we are not running on android. + // See https://github.com/netty/netty/issues/2604 + PlatformDependent0.freeDirectBuffer(buffer); + } + } + + public static long directBufferAddress(ByteBuffer buffer) { + return PlatformDependent0.directBufferAddress(buffer); + } + + public static Object getObject(Object object, long fieldOffset) { + return PlatformDependent0.getObject(object, fieldOffset); + } + + public static Object getObjectVolatile(Object object, long fieldOffset) { + return PlatformDependent0.getObjectVolatile(object, fieldOffset); + } + + public static int getInt(Object object, long fieldOffset) { + return PlatformDependent0.getInt(object, fieldOffset); + } + + public static long objectFieldOffset(Field field) { + return PlatformDependent0.objectFieldOffset(field); + } + + public static byte getByte(long address) { + return PlatformDependent0.getByte(address); + } + + public static short getShort(long address) { + return PlatformDependent0.getShort(address); + } + + public static int getInt(long address) { + return PlatformDependent0.getInt(address); + } + + public static long getLong(long address) { + return PlatformDependent0.getLong(address); + } + + public static void putOrderedObject(Object object, long address, Object value) { + PlatformDependent0.putOrderedObject(object, address, value); + } + + public static void putByte(long address, byte value) { + PlatformDependent0.putByte(address, value); + } + + public static void putShort(long address, short value) { + PlatformDependent0.putShort(address, value); + } + + public static void putInt(long address, int value) { + PlatformDependent0.putInt(address, value); + } + + public static void putLong(long address, long value) { + PlatformDependent0.putLong(address, value); + } + + public static void copyMemory(long srcAddr, long dstAddr, long length) { + PlatformDependent0.copyMemory(srcAddr, dstAddr, length); + } + + public static void copyMemory(byte[] src, int srcIndex, long dstAddr, long length) { + PlatformDependent0.copyMemory(src, ARRAY_BASE_OFFSET + srcIndex, null, dstAddr, length); + } + + public static void copyMemory(long srcAddr, byte[] dst, int dstIndex, long length) { + PlatformDependent0.copyMemory(null, srcAddr, dst, ARRAY_BASE_OFFSET + dstIndex, length); + } + + /** + * Create a new optimized {@link AtomicReferenceFieldUpdater} or {@code null} if it + * could not be created. Because of this the caller need to check for {@code null} and if {@code null} is returned + * use {@link AtomicReferenceFieldUpdater#newUpdater(Class, Class, String)} as fallback. + */ + public static AtomicReferenceFieldUpdater newAtomicReferenceFieldUpdater( + Class tclass, String fieldName) { + if (hasUnsafe()) { + try { + return PlatformDependent0.newAtomicReferenceFieldUpdater(tclass, fieldName); + } catch (Throwable ignore) { + // ignore + } + } + return null; + } + + /** + * Create a new optimized {@link AtomicIntegerFieldUpdater} or {@code null} if it + * could not be created. Because of this the caller need to check for {@code null} and if {@code null} is returned + * use {@link AtomicIntegerFieldUpdater#newUpdater(Class, String)} as fallback. + */ + public static AtomicIntegerFieldUpdater newAtomicIntegerFieldUpdater( + Class tclass, String fieldName) { + if (hasUnsafe()) { + try { + return PlatformDependent0.newAtomicIntegerFieldUpdater(tclass, fieldName); + } catch (Throwable ignore) { + // ignore + } + } + return null; + } + + /** + * Create a new optimized {@link AtomicLongFieldUpdater} or {@code null} if it + * could not be created. Because of this the caller need to check for {@code null} and if {@code null} is returned + * use {@link AtomicLongFieldUpdater#newUpdater(Class, String)} as fallback. + */ + public static AtomicLongFieldUpdater newAtomicLongFieldUpdater( + Class tclass, String fieldName) { + if (hasUnsafe()) { + try { + return PlatformDependent0.newAtomicLongFieldUpdater(tclass, fieldName); + } catch (Throwable ignore) { + // ignore + } + } + return null; + } + + /** + * Create a new {@link Queue} which is safe to use for multiple producers (different threads) and a single + * consumer (one thread!). + */ + public static Queue newMpscQueue() { + return new MpscLinkedQueue(); + } + + /** + * Return the {@link ClassLoader} for the given {@link Class}. + */ + public static ClassLoader getClassLoader(final Class clazz) { + return PlatformDependent0.getClassLoader(clazz); + } + + /** + * Return the context {@link ClassLoader} for the current {@link Thread}. + */ + public static ClassLoader getContextClassLoader() { + return PlatformDependent0.getContextClassLoader(); + } + + /** + * Return the system {@link ClassLoader}. + */ + public static ClassLoader getSystemClassLoader() { + return PlatformDependent0.getSystemClassLoader(); + } + + private static boolean isAndroid0() { + boolean android; + try { + Class.forName("android.app.Application", false, getSystemClassLoader()); + android = true; + } catch (Exception e) { + // Failed to load the class uniquely available in Android. + android = false; + } + + if (android) { + logger.debug("Platform: Android"); + } + return android; + } + + private static boolean isWindows0() { + boolean windows = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).contains("win"); + if (windows) { + logger.debug("Platform: Windows"); + } + return windows; + } + + private static boolean isRoot0() { + if (isWindows()) { + return false; + } + + String[] ID_COMMANDS = { "/usr/bin/id", "/bin/id", "id", "/usr/xpg4/bin/id"}; + Pattern UID_PATTERN = Pattern.compile("^(?:0|[1-9][0-9]*)$"); + for (String idCmd: ID_COMMANDS) { + Process p = null; + BufferedReader in = null; + String uid = null; + try { + p = Runtime.getRuntime().exec(new String[] { idCmd, "-u" }); + in = new BufferedReader(new InputStreamReader(p.getInputStream(), CharsetUtil.US_ASCII)); + uid = in.readLine(); + in.close(); + + for (;;) { + try { + int exitCode = p.waitFor(); + if (exitCode != 0) { + uid = null; + } + break; + } catch (InterruptedException e) { + // Ignore + } + } + } catch (Exception e) { + // Failed to run the command. + uid = null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // Ignore + } + } + if (p != null) { + try { + p.destroy(); + } catch (Exception e) { + // Android sometimes triggers an ErrnoException. + } + } + } + + if (uid != null && UID_PATTERN.matcher(uid).matches()) { + logger.debug("UID: {}", uid); + return "0".equals(uid); + } + } + + logger.debug("Could not determine the current UID using /usr/bin/id; attempting to bind at privileged ports."); + + Pattern PERMISSION_DENIED = Pattern.compile(".*(?:denied|not.*permitted).*"); + for (int i = 1023; i > 0; i --) { + ServerSocket ss = null; + try { + ss = new ServerSocket(); + ss.setReuseAddress(true); + ss.bind(new InetSocketAddress(i)); + if (logger.isDebugEnabled()) { + logger.debug("UID: 0 (succeded to bind at port {})", i); + } + return true; + } catch (Exception e) { + // Failed to bind. + // Check the error message so that we don't always need to bind 1023 times. + String message = e.getMessage(); + if (message == null) { + message = ""; + } + message = message.toLowerCase(); + if (PERMISSION_DENIED.matcher(message).matches()) { + break; + } + } finally { + if (ss != null) { + try { + ss.close(); + } catch (Exception e) { + // Ignore. + } + } + } + } + + logger.debug("UID: non-root (failed to bind at any privileged ports)"); + return false; + } + + + private static int javaVersion0() { + int javaVersion; + + // Not really a loop + for (;;) { + // Android + if (isAndroid()) { + javaVersion = 6; + break; + } + + try { + Class.forName("java.time.Clock", false, getClassLoader(Object.class)); + javaVersion = 8; + break; + } catch (Exception e) { + // Ignore + } + + try { + Class.forName("java.util.concurrent.LinkedTransferQueue", false, getClassLoader(BlockingQueue.class)); + javaVersion = 7; + break; + } catch (Exception e) { + // Ignore + } + + javaVersion = 6; + break; + } + + if (logger.isDebugEnabled()) { + logger.debug("Java version: {}", javaVersion); + } + return javaVersion; + } + + private static boolean hasUnsafe0() { + boolean noUnsafe = SystemPropertyUtil.getBoolean("game.net.noUnsafe", false); + logger.debug("-Dgame.net.noUnsafe: {}", noUnsafe); + + if (isAndroid()) { + logger.debug("sun.misc.Unsafe: unavailable (Android)"); + return false; + } + + if (noUnsafe) { + logger.debug("sun.misc.Unsafe: unavailable (game.net.noUnsafe)"); + return false; + } + + // Legacy properties + boolean tryUnsafe; + if (SystemPropertyUtil.contains("game.net.tryUnsafe")) { + tryUnsafe = SystemPropertyUtil.getBoolean("game.net.tryUnsafe", true); + } else { + tryUnsafe = SystemPropertyUtil.getBoolean("org.jboss.netty.tryUnsafe", true); + } + + if (!tryUnsafe) { + logger.debug("sun.misc.Unsafe: unavailable (game.net.tryUnsafe/org.jboss.netty.tryUnsafe)"); + return false; + } + + try { + boolean hasUnsafe = PlatformDependent0.hasUnsafe(); + logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable"); + return hasUnsafe; + } catch (Throwable t) { + // Probably failed to initialize PlatformDependent0. + return false; + } + } + + private static long arrayBaseOffset0() { + if (!hasUnsafe()) { + return -1; + } + + return PlatformDependent0.arrayBaseOffset(); + } + + private static long maxDirectMemory0() { + long maxDirectMemory = 0; + try { + // Try to get from sun.misc.VM.maxDirectMemory() which should be most accurate. + Class vmClass = Class.forName("sun.misc.VM", true, getSystemClassLoader()); + Method m = vmClass.getDeclaredMethod("maxDirectMemory"); + maxDirectMemory = ((Number) m.invoke(null)).longValue(); + } catch (Throwable t) { + // Ignore + } + + if (maxDirectMemory > 0) { + return maxDirectMemory; + } + + try { + // Now try to get the JVM option (-XX:MaxDirectMemorySize) and parse it. + // Note that we are using reflection because Android doesn't have these classes. + Class mgmtFactoryClass = Class.forName( + "java.lang.management.ManagementFactory", true, getSystemClassLoader()); + Class runtimeClass = Class.forName( + "java.lang.management.RuntimeMXBean", true, getSystemClassLoader()); + + Object runtime = mgmtFactoryClass.getDeclaredMethod("getRuntimeMXBean").invoke(null); + + + List vmArgs = (List) runtimeClass.getDeclaredMethod("getInputArguments").invoke(runtime); + for (int i = vmArgs.size() - 1; i >= 0; i --) { + Matcher m = MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN.matcher(vmArgs.get(i)); + if (!m.matches()) { + continue; + } + + maxDirectMemory = Long.parseLong(m.group(1)); + switch (m.group(2).charAt(0)) { + case 'k': case 'K': + maxDirectMemory *= 1024; + break; + case 'm': case 'M': + maxDirectMemory *= 1024 * 1024; + break; + case 'g': case 'G': + maxDirectMemory *= 1024 * 1024 * 1024; + break; + } + break; + } + } catch (Throwable t) { + // Ignore + } + + if (maxDirectMemory <= 0) { + maxDirectMemory = Runtime.getRuntime().maxMemory(); + logger.debug("maxDirectMemory: {} bytes (maybe)", maxDirectMemory); + } else { + logger.debug("maxDirectMemory: {} bytes", maxDirectMemory); + } + + return maxDirectMemory; + } + +// private static boolean hasJavassist0() { +// if (isAndroid()) { +// return false; +// } +// +// boolean noJavassist = SystemPropertyUtil.getBoolean("game.net.noJavassist", false); +// logger.debug("-Dgame.net.noJavassist: {}", noJavassist); +// +// if (noJavassist) { +// logger.debug("Javassist: unavailable (game.net.noJavassist)"); +// return false; +// } +// +// try { +// JavassistTypeParameterMatcherGenerator.generate(Object.class, getClassLoader(PlatformDependent.class)); +// logger.debug("Javassist: available"); +// return true; +// } catch (Throwable t) { +// // Failed to generate a Javassist-based matcher. +// logger.debug("Javassist: unavailable"); +// logger.debug( +// "You don't have Javassist in your class path or you don't have enough permission " + +// "to load dynamically generated classes. Please check the configuration for better performance."); +// return false; +// } +// } + +// private static File tmpdir0() { +// File f; +// try { +// f = toDirectory(SystemPropertyUtil.get("game.net.tmpdir")); +// if (f != null) { +// logger.debug("-Dgame.net.tmpdir: {}", f); +// return f; +// } +// +// f = toDirectory(SystemPropertyUtil.get("java.io.tmpdir")); +// if (f != null) { +// logger.debug("-Dgame.net.tmpdir: {} (java.io.tmpdir)", f); +// return f; +// } +// +// // This shouldn't happen, but just in case .. +// if (isWindows()) { +// f = toDirectory(System.getenv("TEMP")); +// if (f != null) { +// logger.debug("-Dgame.net.tmpdir: {} (%TEMP%)", f); +// return f; +// } +// +// String userprofile = System.getenv("USERPROFILE"); +// if (userprofile != null) { +// f = toDirectory(userprofile + "\\AppData\\Local\\Temp"); +// if (f != null) { +// logger.debug("-Dgame.net.tmpdir: {} (%USERPROFILE%\\AppData\\Local\\Temp)", f); +// return f; +// } +// +// f = toDirectory(userprofile + "\\Local Settings\\Temp"); +// if (f != null) { +// logger.debug("-Dgame.net.tmpdir: {} (%USERPROFILE%\\Local Settings\\Temp)", f); +// return f; +// } +// } +// } else { +// f = toDirectory(System.getenv("TMPDIR")); +// if (f != null) { +// logger.debug("-Dgame.net.tmpdir: {} ($TMPDIR)", f); +// return f; +// } +// } +// } catch (Exception ignored) { +// // Environment variable inaccessible +// } +// +// // Last resort. +// if (isWindows()) { +// f = new File("C:\\Windows\\Temp"); +// } else { +// f = new File("/tmp"); +// } +// +// logger.warn("Failed to get the temporary directory; falling back to: {}", f); +// return f; +// } +// +// +// private static File toDirectory(String path) { +// if (path == null) { +// return null; +// } +// +// File f = new File(path); +// f.mkdirs(); +// +// if (!f.isDirectory()) { +// return null; +// } +// +// try { +// return f.getAbsoluteFile(); +// } catch (Exception ignored) { +// return f; +// } +// } + +// private static int bitMode0() { +// // Check user-specified bit mode first. +// int bitMode = SystemPropertyUtil.getInt("game.net.bitMode", 0); +// if (bitMode > 0) { +// logger.debug("-Dgame.net.bitMode: {}", bitMode); +// return bitMode; +// } +// +// // And then the vendor specific ones which is probably most reliable. +// bitMode = SystemPropertyUtil.getInt("sun.arch.data.model", 0); +// if (bitMode > 0) { +// logger.debug("-Dgame.net.bitMode: {} (sun.arch.data.model)", bitMode); +// return bitMode; +// } +// bitMode = SystemPropertyUtil.getInt("com.ibm.vm.bitmode", 0); +// if (bitMode > 0) { +// logger.debug("-Dgame.net.bitMode: {} (com.ibm.vm.bitmode)", bitMode); +// return bitMode; +// } +// +// // os.arch also gives us a good hint. +// String arch = SystemPropertyUtil.get("os.arch", "").toLowerCase(Locale.US).trim(); +// if ("amd64".equals(arch) || "x86_64".equals(arch)) { +// bitMode = 64; +// } else if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686".equals(arch)) { +// bitMode = 32; +// } +// +// if (bitMode > 0) { +// logger.debug("-Dgame.net.bitMode: {} (os.arch: {})", bitMode, arch); +// } +// +// // Last resort: guess from VM name and then fall back to most common 64-bit mode. +// String vm = SystemPropertyUtil.get("java.vm.name", "").toLowerCase(Locale.US); +// Pattern BIT_PATTERN = Pattern.compile("([1-9][0-9]+)-?bit"); +// Matcher m = BIT_PATTERN.matcher(vm); +// if (m.find()) { +// return Integer.parseInt(m.group(1)); +// } else { +// return 64; +// } +// } + +// private static int addressSize0() { +// if (!hasUnsafe()) { +// return -1; +// } +// return PlatformDependent0.addressSize(); +// } + + private PlatformDependent() { + // only static method supported + } +} diff --git a/common/src/common/net/util/internal/PlatformDependent0.java b/common/src/common/net/util/internal/PlatformDependent0.java new file mode 100644 index 0000000..ce3c3cc --- /dev/null +++ b/common/src/common/net/util/internal/PlatformDependent0.java @@ -0,0 +1,383 @@ +/* + * Copyright 2013 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 common.net.util.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; +import sun.misc.Unsafe; + +/** + * The {@link PlatformDependent} operations which requires access to {@code sun.misc.*}. + */ +final class PlatformDependent0 { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent0.class); + private static final Unsafe UNSAFE; + private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + private static final long ADDRESS_FIELD_OFFSET; + + /** + * Limits the number of bytes to copy per {@link Unsafe#copyMemory(long, long, long)} to allow safepoint polling + * during a large copy. + */ + private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L; + + /** + * {@code true} if and only if the platform supports unaligned access. + * + * @see Wikipedia on segfault + */ + private static final boolean UNALIGNED; + + static { + ByteBuffer direct = ByteBuffer.allocateDirect(1); + Field addressField; + try { + addressField = Buffer.class.getDeclaredField("address"); + addressField.setAccessible(true); + if (addressField.getLong(ByteBuffer.allocate(1)) != 0) { + // A heap buffer must have 0 address. + addressField = null; + } else { + if (addressField.getLong(direct) == 0) { + // A direct buffer must have non-zero address. + addressField = null; + } + } + } catch (Throwable t) { + // Failed to access the address field. + addressField = null; + } + logger.debug("java.nio.Buffer.address: {}", addressField != null? "available" : "unavailable"); + + Unsafe unsafe; + if (addressField != null) { + try { + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (Unsafe) unsafeField.get(null); + logger.debug("sun.misc.Unsafe.theUnsafe: {}", unsafe != null ? "available" : "unavailable"); + + // Ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK. + // https://github.com/netty/netty/issues/1061 + // http://www.mail-archive.com/jdk6-dev@openjdk.java.net/msg00698.html + try { + if (unsafe != null) { + unsafe.getClass().getDeclaredMethod( + "copyMemory", Object.class, long.class, Object.class, long.class, long.class); + logger.debug("sun.misc.Unsafe.copyMemory: available"); + } + } catch (NoSuchMethodError t) { + logger.debug("sun.misc.Unsafe.copyMemory: unavailable"); + throw t; + } catch (NoSuchMethodException e) { + logger.debug("sun.misc.Unsafe.copyMemory: unavailable"); + throw e; + } + } catch (Throwable cause) { + // Unsafe.copyMemory(Object, long, Object, long, long) unavailable. + unsafe = null; + } + } else { + // If we cannot access the address of a direct buffer, there's no point of using unsafe. + // Let's just pretend unsafe is unavailable for overall simplicity. + unsafe = null; + } + + UNSAFE = unsafe; + + if (unsafe == null) { + ADDRESS_FIELD_OFFSET = -1; + UNALIGNED = false; + } else { + ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField); + boolean unaligned; + try { + Class bitsClass = Class.forName("java.nio.Bits", false, ClassLoader.getSystemClassLoader()); + Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned"); + unalignedMethod.setAccessible(true); + unaligned = Boolean.TRUE.equals(unalignedMethod.invoke(null)); + } catch (Throwable t) { + // We at least know x86 and x64 support unaligned access. + String arch = SystemPropertyUtil.get("os.arch", ""); + //noinspection DynamicRegexReplaceableByCompiledPattern + unaligned = arch.matches("^(i[3-6]86|x86(_64)?|x64|amd64)$"); + } + + UNALIGNED = unaligned; + logger.debug("java.nio.Bits.unaligned: {}", UNALIGNED); + } + } + + static boolean hasUnsafe() { + return UNSAFE != null; + } + + static void throwException(Throwable t) { + UNSAFE.throwException(t); + } + + static void freeDirectBuffer(ByteBuffer buffer) { + // Delegate to other class to not break on android + // See https://github.com/netty/netty/issues/2604 + Cleaner0.freeDirectBuffer(buffer); + } + + static long directBufferAddress(ByteBuffer buffer) { + return getLong(buffer, ADDRESS_FIELD_OFFSET); + } + + static long arrayBaseOffset() { + return UNSAFE.arrayBaseOffset(byte[].class); + } + + static Object getObject(Object object, long fieldOffset) { + return UNSAFE.getObject(object, fieldOffset); + } + + static Object getObjectVolatile(Object object, long fieldOffset) { + return UNSAFE.getObjectVolatile(object, fieldOffset); + } + + static int getInt(Object object, long fieldOffset) { + return UNSAFE.getInt(object, fieldOffset); + } + + private static long getLong(Object object, long fieldOffset) { + return UNSAFE.getLong(object, fieldOffset); + } + + static long objectFieldOffset(Field field) { + return UNSAFE.objectFieldOffset(field); + } + + static byte getByte(long address) { + return UNSAFE.getByte(address); + } + + static short getShort(long address) { + if (UNALIGNED) { + return UNSAFE.getShort(address); + } else if (BIG_ENDIAN) { + return (short) (getByte(address) << 8 | getByte(address + 1) & 0xff); + } else { + return (short) (getByte(address + 1) << 8 | getByte(address) & 0xff); + } + } + + static int getInt(long address) { + if (UNALIGNED) { + return UNSAFE.getInt(address); + } else if (BIG_ENDIAN) { + return getByte(address) << 24 | + (getByte(address + 1) & 0xff) << 16 | + (getByte(address + 2) & 0xff) << 8 | + getByte(address + 3) & 0xff; + } else { + return getByte(address + 3) << 24 | + (getByte(address + 2) & 0xff) << 16 | + (getByte(address + 1) & 0xff) << 8 | + getByte(address) & 0xff; + } + } + + static long getLong(long address) { + if (UNALIGNED) { + return UNSAFE.getLong(address); + } else if (BIG_ENDIAN) { + return (long) getByte(address) << 56 | + ((long) getByte(address + 1) & 0xff) << 48 | + ((long) getByte(address + 2) & 0xff) << 40 | + ((long) getByte(address + 3) & 0xff) << 32 | + ((long) getByte(address + 4) & 0xff) << 24 | + ((long) getByte(address + 5) & 0xff) << 16 | + ((long) getByte(address + 6) & 0xff) << 8 | + (long) getByte(address + 7) & 0xff; + } else { + return (long) getByte(address + 7) << 56 | + ((long) getByte(address + 6) & 0xff) << 48 | + ((long) getByte(address + 5) & 0xff) << 40 | + ((long) getByte(address + 4) & 0xff) << 32 | + ((long) getByte(address + 3) & 0xff) << 24 | + ((long) getByte(address + 2) & 0xff) << 16 | + ((long) getByte(address + 1) & 0xff) << 8 | + (long) getByte(address) & 0xff; + } + } + + static void putOrderedObject(Object object, long address, Object value) { + UNSAFE.putOrderedObject(object, address, value); + } + + static void putByte(long address, byte value) { + UNSAFE.putByte(address, value); + } + + static void putShort(long address, short value) { + if (UNALIGNED) { + UNSAFE.putShort(address, value); + } else if (BIG_ENDIAN) { + putByte(address, (byte) (value >>> 8)); + putByte(address + 1, (byte) value); + } else { + putByte(address + 1, (byte) (value >>> 8)); + putByte(address, (byte) value); + } + } + + static void putInt(long address, int value) { + if (UNALIGNED) { + UNSAFE.putInt(address, value); + } else if (BIG_ENDIAN) { + putByte(address, (byte) (value >>> 24)); + putByte(address + 1, (byte) (value >>> 16)); + putByte(address + 2, (byte) (value >>> 8)); + putByte(address + 3, (byte) value); + } else { + putByte(address + 3, (byte) (value >>> 24)); + putByte(address + 2, (byte) (value >>> 16)); + putByte(address + 1, (byte) (value >>> 8)); + putByte(address, (byte) value); + } + } + + static void putLong(long address, long value) { + if (UNALIGNED) { + UNSAFE.putLong(address, value); + } else if (BIG_ENDIAN) { + putByte(address, (byte) (value >>> 56)); + putByte(address + 1, (byte) (value >>> 48)); + putByte(address + 2, (byte) (value >>> 40)); + putByte(address + 3, (byte) (value >>> 32)); + putByte(address + 4, (byte) (value >>> 24)); + putByte(address + 5, (byte) (value >>> 16)); + putByte(address + 6, (byte) (value >>> 8)); + putByte(address + 7, (byte) value); + } else { + putByte(address + 7, (byte) (value >>> 56)); + putByte(address + 6, (byte) (value >>> 48)); + putByte(address + 5, (byte) (value >>> 40)); + putByte(address + 4, (byte) (value >>> 32)); + putByte(address + 3, (byte) (value >>> 24)); + putByte(address + 2, (byte) (value >>> 16)); + putByte(address + 1, (byte) (value >>> 8)); + putByte(address, (byte) value); + } + } + + static void copyMemory(long srcAddr, long dstAddr, long length) { + //UNSAFE.copyMemory(srcAddr, dstAddr, length); + while (length > 0) { + long size = Math.min(length, UNSAFE_COPY_THRESHOLD); + UNSAFE.copyMemory(srcAddr, dstAddr, size); + length -= size; + srcAddr += size; + dstAddr += size; + } + } + + static void copyMemory(Object src, long srcOffset, Object dst, long dstOffset, long length) { + //UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length); + while (length > 0) { + long size = Math.min(length, UNSAFE_COPY_THRESHOLD); + UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, size); + length -= size; + srcOffset += size; + dstOffset += size; + } + } + + static AtomicReferenceFieldUpdater newAtomicReferenceFieldUpdater( + Class tclass, String fieldName) throws Exception { + return new UnsafeAtomicReferenceFieldUpdater(UNSAFE, tclass, fieldName); + } + + static AtomicIntegerFieldUpdater newAtomicIntegerFieldUpdater( + Class tclass, String fieldName) throws Exception { + return new UnsafeAtomicIntegerFieldUpdater(UNSAFE, tclass, fieldName); + } + + static AtomicLongFieldUpdater newAtomicLongFieldUpdater( + Class tclass, String fieldName) throws Exception { + return new UnsafeAtomicLongFieldUpdater(UNSAFE, tclass, fieldName); + } + + static ClassLoader getClassLoader(final Class clazz) { + if (System.getSecurityManager() == null) { + return clazz.getClassLoader(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ClassLoader run() { + return clazz.getClassLoader(); + } + }); + } + } + + static ClassLoader getContextClassLoader() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + } + } + + static ClassLoader getSystemClassLoader() { + if (System.getSecurityManager() == null) { + return ClassLoader.getSystemClassLoader(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ClassLoader run() { + return ClassLoader.getSystemClassLoader(); + } + }); + } + } + + static int addressSize() { + return UNSAFE.addressSize(); + } + + static long allocateMemory(long size) { + return UNSAFE.allocateMemory(size); + } + + static void freeMemory(long address) { + UNSAFE.freeMemory(address); + } + + private PlatformDependent0() { + } + +} diff --git a/common/src/common/net/util/internal/RecyclableArrayList.java b/common/src/common/net/util/internal/RecyclableArrayList.java new file mode 100644 index 0000000..739a077 --- /dev/null +++ b/common/src/common/net/util/internal/RecyclableArrayList.java @@ -0,0 +1,132 @@ +/* + * Copyright 2013 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 common.net.util.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; + +import common.net.util.Recycler; +import common.net.util.Recycler.Handle; + +/** + * A simple list which is reyclable. This implementation does not allow {@code null} elements to be added. + */ +public final class RecyclableArrayList extends ArrayList { + + private static final long serialVersionUID = -8605125654176467947L; + + private static final int DEFAULT_INITIAL_CAPACITY = 8; + + private static final Recycler RECYCLER = new Recycler() { + @Override + protected RecyclableArrayList newObject(Handle handle) { + return new RecyclableArrayList(handle); + } + }; + + /** + * Create a new empty {@link RecyclableArrayList} instance + */ + public static RecyclableArrayList newInstance() { + return newInstance(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Create a new empty {@link RecyclableArrayList} instance with the given capacity. + */ + public static RecyclableArrayList newInstance(int minCapacity) { + RecyclableArrayList ret = RECYCLER.get(); + ret.ensureCapacity(minCapacity); + return ret; + } + + private final Handle handle; + + private RecyclableArrayList(Handle handle) { + this(handle, DEFAULT_INITIAL_CAPACITY); + } + + private RecyclableArrayList(Handle handle, int initialCapacity) { + super(initialCapacity); + this.handle = handle; + } + + @Override + public boolean addAll(Collection c) { + checkNullElements(c); + return super.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + checkNullElements(c); + return super.addAll(index, c); + } + + private static void checkNullElements(Collection c) { + if (c instanceof RandomAccess && c instanceof List) { + // produce less garbage + List list = (List) c; + int size = list.size(); + for (int i = 0; i < size; i++) { + if (list.get(i) == null) { + throw new IllegalArgumentException("c contains null values"); + } + } + } else { + for (Object element: c) { + if (element == null) { + throw new IllegalArgumentException("c contains null values"); + } + } + } + } + + @Override + public boolean add(Object element) { + if (element == null) { + throw new NullPointerException("element"); + } + return super.add(element); + } + + @Override + public void add(int index, Object element) { + if (element == null) { + throw new NullPointerException("element"); + } + super.add(index, element); + } + + @Override + public Object set(int index, Object element) { + if (element == null) { + throw new NullPointerException("element"); + } + return super.set(index, element); + } + + /** + * Clear and recycle this instance. + */ + public boolean recycle() { + clear(); + return RECYCLER.recycle(this, handle); + } +} diff --git a/common/src/common/net/util/internal/RecyclableMpscLinkedQueueNode.java b/common/src/common/net/util/internal/RecyclableMpscLinkedQueueNode.java new file mode 100644 index 0000000..a037070 --- /dev/null +++ b/common/src/common/net/util/internal/RecyclableMpscLinkedQueueNode.java @@ -0,0 +1,45 @@ +/* +* Copyright 2014 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 common.net.util.internal; + +import common.net.util.Recycler; + +/** + * {@link MpscLinkedQueueNode} that will automatically call {@link #recycle(Recycler.Handle)} when the node was + * unlinked. + */ +public abstract class RecyclableMpscLinkedQueueNode extends MpscLinkedQueueNode { + private final Recycler.Handle handle; + + protected RecyclableMpscLinkedQueueNode(Recycler.Handle handle) { + if (handle == null) { + throw new NullPointerException("handle"); + } + this.handle = handle; + } + + @Override + final void unlink() { + super.unlink(); + recycle(handle); + } + + /** + * Called once unliked and so ready to recycled. + */ + protected abstract void recycle(Recycler.Handle handle); +} diff --git a/common/src/common/net/util/internal/StringUtil.java b/common/src/common/net/util/internal/StringUtil.java new file mode 100644 index 0000000..a7e971f --- /dev/null +++ b/common/src/common/net/util/internal/StringUtil.java @@ -0,0 +1,274 @@ +/* + * 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 common.net.util.internal; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.List; + +/** + * String utility class. + */ +public final class StringUtil { + + public static final String NEWLINE; + + private static final String[] BYTE2HEX_PAD = new String[256]; + private static final String[] BYTE2HEX_NOPAD = new String[256]; + private static final String EMPTY_STRING = ""; + + static { + // Determine the newline character of the current platform. + String newLine; + Formatter formatter = null; + + try { + formatter = new Formatter(); + newLine = formatter.format("%n").toString(); + } catch (Exception e) { + // Should not reach here, but just in case. + newLine = "\n"; + } + finally { + if(formatter != null) { + try { + formatter.close(); + } + catch(Throwable e) { + } + } + } + + NEWLINE = newLine; + + // Generate the lookup table that converts a byte into a 2-digit hexadecimal integer. + int i; + for (i = 0; i < 10; i ++) { + StringBuilder buf = new StringBuilder(2); + buf.append('0'); + buf.append(i); + BYTE2HEX_PAD[i] = buf.toString(); + BYTE2HEX_NOPAD[i] = String.valueOf(i); + } + for (; i < 16; i ++) { + StringBuilder buf = new StringBuilder(2); + char c = (char) ('a' + i - 10); + buf.append('0'); + buf.append(c); + BYTE2HEX_PAD[i] = buf.toString(); + BYTE2HEX_NOPAD[i] = String.valueOf(c); + } + for (; i < BYTE2HEX_PAD.length; i ++) { + StringBuilder buf = new StringBuilder(2); + buf.append(Integer.toHexString(i)); + String str = buf.toString(); + BYTE2HEX_PAD[i] = str; + BYTE2HEX_NOPAD[i] = str; + } + } + + /** + * Splits the specified {@link String} with the specified delimiter. This operation is a simplified and optimized + * version of {@link String#split(String)}. + */ + public static String[] split(String value, char delim) { + final int end = value.length(); + final List res = new ArrayList(); + + int start = 0; + for (int i = 0; i < end; i ++) { + if (value.charAt(i) == delim) { + if (start == i) { + res.add(EMPTY_STRING); + } else { + res.add(value.substring(start, i)); + } + start = i + 1; + } + } + + if (start == 0) { // If no delimiter was found in the value + res.add(value); + } else { + if (start != end) { + // Add the last element if it's not empty. + res.add(value.substring(start, end)); + } else { + // Truncate trailing empty elements. + for (int i = res.size() - 1; i >= 0; i --) { + if (res.get(i).isEmpty()) { + res.remove(i); + } else { + break; + } + } + } + } + + return res.toArray(new String[res.size()]); + } + + /** + * Converts the specified byte value into a 2-digit hexadecimal integer. + */ + public static String byteToHexStringPadded(int value) { + return BYTE2HEX_PAD[value & 0xff]; + } + + /** + * Converts the specified byte value into a 2-digit hexadecimal integer and appends it to the specified buffer. + */ + public static T byteToHexStringPadded(T buf, int value) { + try { + buf.append(byteToHexStringPadded(value)); + } catch (IOException e) { + PlatformDependent.throwException(e); + } + return buf; + } + + /** + * Converts the specified byte array into a hexadecimal value. + */ + public static String toHexStringPadded(byte[] src) { + return toHexStringPadded(src, 0, src.length); + } + + /** + * Converts the specified byte array into a hexadecimal value. + */ + public static String toHexStringPadded(byte[] src, int offset, int length) { + return toHexStringPadded(new StringBuilder(length << 1), src, offset, length).toString(); + } + + /** + * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer. + */ + public static T toHexStringPadded(T dst, byte[] src) { + return toHexStringPadded(dst, src, 0, src.length); + } + + /** + * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer. + */ + public static T toHexStringPadded(T dst, byte[] src, int offset, int length) { + final int end = offset + length; + for (int i = offset; i < end; i ++) { + byteToHexStringPadded(dst, src[i]); + } + return dst; + } + + /** + * Converts the specified byte value into a hexadecimal integer. + */ + public static String byteToHexString(int value) { + return BYTE2HEX_NOPAD[value & 0xff]; + } + + /** + * Converts the specified byte value into a hexadecimal integer and appends it to the specified buffer. + */ + public static T byteToHexString(T buf, int value) { + try { + buf.append(byteToHexString(value)); + } catch (IOException e) { + PlatformDependent.throwException(e); + } + return buf; + } + + /** + * Converts the specified byte array into a hexadecimal value. + */ + public static String toHexString(byte[] src) { + return toHexString(src, 0, src.length); + } + + /** + * Converts the specified byte array into a hexadecimal value. + */ + public static String toHexString(byte[] src, int offset, int length) { + return toHexString(new StringBuilder(length << 1), src, offset, length).toString(); + } + + /** + * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer. + */ + public static T toHexString(T dst, byte[] src) { + return toHexString(dst, src, 0, src.length); + } + + /** + * Converts the specified byte array into a hexadecimal value and appends it to the specified buffer. + */ + public static T toHexString(T dst, byte[] src, int offset, int length) { + assert length >= 0; + if (length == 0) { + return dst; + } + + final int end = offset + length; + final int endMinusOne = end - 1; + int i; + + // Skip preceding zeroes. + for (i = offset; i < endMinusOne; i ++) { + if (src[i] != 0) { + break; + } + } + + byteToHexString(dst, src[i ++]); + int remaining = end - i; + toHexStringPadded(dst, src, i, remaining); + + return dst; + } + + /** + * The shortcut to {@link #simpleClassName(Class) simpleClassName(o.getClass())}. + */ + public static String simpleClassName(Object o) { + if (o == null) { + return "null_object"; + } else { + return simpleClassName(o.getClass()); + } + } + + /** + * Generates a simplified name from a {@link Class}. Similar to {@link Class#getSimpleName()}, but it works fine + * with anonymous classes. + */ + public static String simpleClassName(Class clazz) { + if (clazz == null) { + return "null_class"; + } + + Package pkg = clazz.getPackage(); + if (pkg != null) { + return clazz.getName().substring(pkg.getName().length() + 1); + } else { + return clazz.getName(); + } + } + + private StringUtil() { + // Unused. + } +} diff --git a/common/src/common/net/util/internal/SystemPropertyUtil.java b/common/src/common/net/util/internal/SystemPropertyUtil.java new file mode 100644 index 0000000..0aea0e4 --- /dev/null +++ b/common/src/common/net/util/internal/SystemPropertyUtil.java @@ -0,0 +1,223 @@ +/* + * 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 common.net.util.internal; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A collection of utility methods to retrieve and parse the values of the Java system properties. + */ +public final class SystemPropertyUtil { + + private static boolean initializedLogger; + private static final InternalLogger logger; + private static boolean loggedException; + + static { + initializedLogger = false; + logger = InternalLoggerFactory.getInstance(SystemPropertyUtil.class); + initializedLogger = true; + } + + /** + * Returns {@code true} if and only if the system property with the specified {@code key} + * exists. + */ + public static boolean contains(String key) { + return get(key) != null; + } + + /** + * Returns the value of the Java system property with the specified + * {@code key}, while falling back to {@code null} if the property access fails. + * + * @return the property value or {@code null} + */ + public static String get(String key) { + return get(key, null); + } + + /** + * Returns the value of the Java system property with the specified + * {@code key}, while falling back to the specified default value if + * the property access fails. + * + * @return the property value. + * {@code def} if there's no such property or if an access to the + * specified property is not allowed. + */ + public static String get(final String key, String def) { + if (key == null) { + throw new NullPointerException("key"); + } + if (key.isEmpty()) { + throw new IllegalArgumentException("key must not be empty."); + } + + String value = null; + try { + if (System.getSecurityManager() == null) { + value = System.getProperty(key); + } else { + value = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return System.getProperty(key); + } + }); + } + } catch (Exception e) { + if (!loggedException) { + log("Unable to retrieve a system property '" + key + "'; default values will be used.", e); + loggedException = true; + } + } + + if (value == null) { + return def; + } + + return value; + } + + /** + * Returns the value of the Java system property with the specified + * {@code key}, while falling back to the specified default value if + * the property access fails. + * + * @return the property value. + * {@code def} if there's no such property or if an access to the + * specified property is not allowed. + */ + public static boolean getBoolean(String key, boolean def) { + String value = get(key); + if (value == null) { + return def; + } + + value = value.trim().toLowerCase(); + if (value.isEmpty()) { + return true; + } + + if ("true".equals(value) || "yes".equals(value) || "1".equals(value)) { + return true; + } + + if ("false".equals(value) || "no".equals(value) || "0".equals(value)) { + return false; + } + + log( + "Unable to parse the boolean system property '" + key + "':" + value + " - " + + "using the default value: " + def); + + return def; + } + + private static final Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); + + /** + * Returns the value of the Java system property with the specified + * {@code key}, while falling back to the specified default value if + * the property access fails. + * + * @return the property value. + * {@code def} if there's no such property or if an access to the + * specified property is not allowed. + */ + public static int getInt(String key, int def) { + String value = get(key); + if (value == null) { + return def; + } + + value = value.trim().toLowerCase(); + if (INTEGER_PATTERN.matcher(value).matches()) { + try { + return Integer.parseInt(value); + } catch (Exception e) { + // Ignore + } + } + + log( + "Unable to parse the integer system property '" + key + "':" + value + " - " + + "using the default value: " + def); + + return def; + } + + /** + * Returns the value of the Java system property with the specified + * {@code key}, while falling back to the specified default value if + * the property access fails. + * + * @return the property value. + * {@code def} if there's no such property or if an access to the + * specified property is not allowed. + */ + public static long getLong(String key, long def) { + String value = get(key); + if (value == null) { + return def; + } + + value = value.trim().toLowerCase(); + if (INTEGER_PATTERN.matcher(value).matches()) { + try { + return Long.parseLong(value); + } catch (Exception e) { + // Ignore + } + } + + log( + "Unable to parse the long integer system property '" + key + "':" + value + " - " + + "using the default value: " + def); + + return def; + } + + private static void log(String msg) { + if (initializedLogger) { + logger.warn(msg); + } else { + // Use JDK logging if logger was not initialized yet. + Logger.getLogger(SystemPropertyUtil.class.getName()).log(Level.WARNING, msg); + } + } + + private static void log(String msg, Exception e) { + if (initializedLogger) { + logger.warn(msg, e); + } else { + // Use JDK logging if logger was not initialized yet. + Logger.getLogger(SystemPropertyUtil.class.getName()).log(Level.WARNING, msg, e); + } + } + + private SystemPropertyUtil() { + // Unused + } +} diff --git a/common/src/common/net/util/internal/ThreadLocalRandom.java b/common/src/common/net/util/internal/ThreadLocalRandom.java new file mode 100644 index 0000000..0ad7360 --- /dev/null +++ b/common/src/common/net/util/internal/ThreadLocalRandom.java @@ -0,0 +1,336 @@ +/* + * Copyright 2014 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. + */ + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package common.net.util.internal; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import common.net.util.internal.logging.InternalLogger; +import common.net.util.internal.logging.InternalLoggerFactory; + +/** + * A random number generator isolated to the current thread. Like the + * global {@link java.util.Random} generator used by the {@link + * java.lang.Math} class, a {@code ThreadLocalRandom} is initialized + * with an internally generated seed that may not otherwise be + * modified. When applicable, use of {@code ThreadLocalRandom} rather + * than shared {@code Random} objects in concurrent programs will + * typically encounter much less overhead and contention. Use of + * {@code ThreadLocalRandom} is particularly appropriate when multiple + * tasks (for example, each a {@link game.net.util.internal.chmv8.ForkJoinTask}) use random numbers + * in parallel in thread pools. + * + *

Usages of this class should typically be of the form: + * {@code ThreadLocalRandom.current().nextX(...)} (where + * {@code X} is {@code Int}, {@code Long}, etc). + * When all usages are of this form, it is never possible to + * accidently share a {@code ThreadLocalRandom} across multiple threads. + * + *

This class also provides additional commonly used bounded random + * generation methods. + * + * //since 1.7 + * //author Doug Lea + */ + +public final class ThreadLocalRandom extends Random { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ThreadLocalRandom.class); + + private static final AtomicLong seedUniquifier = new AtomicLong(); + + private static volatile long initialSeedUniquifier; + + public static void setInitialSeedUniquifier(long initialSeedUniquifier) { + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier; + } + + public static synchronized long getInitialSeedUniquifier() { + // Use the value set via the setter. + long initialSeedUniquifier = ThreadLocalRandom.initialSeedUniquifier; + if (initialSeedUniquifier == 0) { + // Use the system property value. + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier = + SystemPropertyUtil.getLong("game.net.initialSeedUniquifier", 0); + } + + // Otherwise, generate one. + if (initialSeedUniquifier == 0) { + // Try to generate a real random number from /dev/random. + // Get from a different thread to avoid blocking indefinitely on a machine without much entrophy. + final BlockingQueue queue = new LinkedBlockingQueue(); + Thread generatorThread = new Thread("initialSeedUniquifierGenerator") { + @Override + public void run() { + SecureRandom random = new SecureRandom(); // Get the real random seed from /dev/random + queue.add(random.generateSeed(8)); + } + }; + generatorThread.setDaemon(true); + generatorThread.start(); + generatorThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.debug("An exception has been raised by {}", t.getName(), e); + } + }); + + // Get the random seed from the thread with timeout. + final long timeoutSeconds = 3; + final long deadLine = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeoutSeconds); + boolean interrupted = false; + for (;;) { + long waitTime = deadLine - System.nanoTime(); + if (waitTime <= 0) { + generatorThread.interrupt(); + logger.warn( + "Failed to generate a seed from SecureRandom within {} seconds. " + + "Not enough entrophy?", timeoutSeconds + ); + break; + } + + try { + byte[] seed = queue.poll(waitTime, TimeUnit.NANOSECONDS); + if (seed != null) { + initialSeedUniquifier = + ((long) seed[0] & 0xff) << 56 | + ((long) seed[1] & 0xff) << 48 | + ((long) seed[2] & 0xff) << 40 | + ((long) seed[3] & 0xff) << 32 | + ((long) seed[4] & 0xff) << 24 | + ((long) seed[5] & 0xff) << 16 | + ((long) seed[6] & 0xff) << 8 | + (long) seed[7] & 0xff; + break; + } + } catch (InterruptedException e) { + interrupted = true; + logger.warn("Failed to generate a seed from SecureRandom due to an InterruptedException."); + break; + } + } + + // Just in case the initialSeedUniquifier is zero or some other constant + initialSeedUniquifier ^= 0x3255ecdc33bae119L; // just a meaningless random number + initialSeedUniquifier ^= Long.reverse(System.nanoTime()); + + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier; + + if (interrupted) { + // Restore the interrupt status because we don't know how to/don't need to handle it here. + Thread.currentThread().interrupt(); + + // Interrupt the generator thread if it's still running, + // in the hope that the SecureRandom provider raises an exception on interruption. + generatorThread.interrupt(); + } + } + + return initialSeedUniquifier; + } + + private static long newSeed() { + final long startTime = System.nanoTime(); + for (;;) { + final long current = seedUniquifier.get(); + final long actualCurrent = current != 0? current : getInitialSeedUniquifier(); + + // L'Ecuyer, "Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure", 1999 + final long next = actualCurrent * 181783497276652981L; + + if (seedUniquifier.compareAndSet(current, next)) { + if (current == 0 && logger.isDebugEnabled()) { + logger.debug(String.format( + "-Dgame.net.initialSeedUniquifier: 0x%016x (took %d ms)", + actualCurrent, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime))); + } + return next ^ System.nanoTime(); + } + } + } + + // same constants as Random, but must be redeclared because private + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + /** + * The random seed. We can't use super.seed. + */ + private long rnd; + + /** + * Initialization flag to permit calls to setSeed to succeed only + * while executing the Random constructor. We can't allow others + * since it would cause setting seed in one part of a program to + * unintentionally impact other usages by the thread. + */ + boolean initialized; + + // Padding to help avoid memory contention among seed updates in + // different TLRs in the common case that they are located near + // each other. + private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + /** + * Constructor called only by localRandom.initialValue. + */ + ThreadLocalRandom() { + super(newSeed()); + initialized = true; + } + + /** + * Returns the current thread's {@code ThreadLocalRandom}. + * + * @return the current thread's {@code ThreadLocalRandom} + */ + public static ThreadLocalRandom current() { + return InternalThreadLocalMap.get().random(); + } + + /** + * Throws {@code UnsupportedOperationException}. Setting seeds in + * this generator is not supported. + * + * @throws UnsupportedOperationException always + */ + public void setSeed(long seed) { + if (initialized) { + throw new UnsupportedOperationException(); + } + rnd = (seed ^ multiplier) & mask; + } + + protected int next(int bits) { + rnd = (rnd * multiplier + addend) & mask; + return (int) (rnd >>> (48 - bits)); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @throws IllegalArgumentException if least greater than or equal + * to bound + * @return the next value + */ + public int nextInt(int least, int bound) { + if (least >= bound) { + throw new IllegalArgumentException(); + } + return nextInt(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public long nextLong(long n) { + if (n <= 0) { + throw new IllegalArgumentException("n must be positive"); + } + + // Divide n by two until small enough for nextInt. On each + // iteration (at most 31 of them but usually much less), + // randomly choose both whether to include high bit in result + // (offset) and whether to continue with the lower vs upper + // half (which makes a difference only if odd). + long offset = 0; + while (n >= Integer.MAX_VALUE) { + int bits = next(2); + long half = n >>> 1; + long nextn = ((bits & 2) == 0) ? half : n - half; + if ((bits & 1) == 0) { + offset += n - nextn; + } + n = nextn; + } + return offset + nextInt((int) n); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public long nextLong(long least, long bound) { + if (least >= bound) { + throw new IllegalArgumentException(); + } + return nextLong(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed {@code double} value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public double nextDouble(double n) { + if (n <= 0) { + throw new IllegalArgumentException("n must be positive"); + } + return nextDouble() * n; + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public double nextDouble(double least, double bound) { + if (least >= bound) { + throw new IllegalArgumentException(); + } + return nextDouble() * (bound - least) + least; + } + + private static final long serialVersionUID = -5851777807851030925L; +} diff --git a/common/src/common/net/util/internal/TypeParameterMatcher.java b/common/src/common/net/util/internal/TypeParameterMatcher.java new file mode 100644 index 0000000..76ba1b2 --- /dev/null +++ b/common/src/common/net/util/internal/TypeParameterMatcher.java @@ -0,0 +1,177 @@ +/* + * Copyright 2013 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 common.net.util.internal; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.HashMap; +import java.util.Map; + +public abstract class TypeParameterMatcher { + + private static final TypeParameterMatcher NOOP = new NoOpTypeParameterMatcher(); + private static final Object TEST_OBJECT = new Object(); + + public static TypeParameterMatcher get(final Class parameterType) { + final Map, TypeParameterMatcher> getCache = + InternalThreadLocalMap.get().typeParameterMatcherGetCache(); + + TypeParameterMatcher matcher = getCache.get(parameterType); + if (matcher == null) { + if (parameterType == Object.class) { + matcher = NOOP; + } +// else if (PlatformDependent.hasJavassist()) { +// try { +// matcher = JavassistTypeParameterMatcherGenerator.generate(parameterType); +// matcher.match(TEST_OBJECT); +// } catch (IllegalAccessError e) { +// // Happens if parameterType is not public. +// matcher = null; +// } catch (Exception e) { +// // Will not usually happen, but just in case. +// matcher = null; +// } +// } + + if (matcher == null) { + matcher = new ReflectiveMatcher(parameterType); + } + + getCache.put(parameterType, matcher); + } + + return matcher; + } + + public static TypeParameterMatcher find( + final Object object, final Class parameterizedSuperclass, final String typeParamName) { + + final Map, Map> findCache = + InternalThreadLocalMap.get().typeParameterMatcherFindCache(); + final Class thisClass = object.getClass(); + + Map map = findCache.get(thisClass); + if (map == null) { + map = new HashMap(); + findCache.put(thisClass, map); + } + + TypeParameterMatcher matcher = map.get(typeParamName); + if (matcher == null) { + matcher = get(find0(object, parameterizedSuperclass, typeParamName)); + map.put(typeParamName, matcher); + } + + return matcher; + } + + private static Class find0( + final Object object, Class parameterizedSuperclass, String typeParamName) { + + final Class thisClass = object.getClass(); + Class currentClass = thisClass; + for (;;) { + if (currentClass.getSuperclass() == parameterizedSuperclass) { + int typeParamIndex = -1; + TypeVariable[] typeParams = currentClass.getSuperclass().getTypeParameters(); + for (int i = 0; i < typeParams.length; i ++) { + if (typeParamName.equals(typeParams[i].getName())) { + typeParamIndex = i; + break; + } + } + + if (typeParamIndex < 0) { + throw new IllegalStateException( + "unknown type parameter '" + typeParamName + "': " + parameterizedSuperclass); + } + + Type genericSuperType = currentClass.getGenericSuperclass(); + if (!(genericSuperType instanceof ParameterizedType)) { + return Object.class; + } + + Type[] actualTypeParams = ((ParameterizedType) genericSuperType).getActualTypeArguments(); + + Type actualTypeParam = actualTypeParams[typeParamIndex]; + if (actualTypeParam instanceof ParameterizedType) { + actualTypeParam = ((ParameterizedType) actualTypeParam).getRawType(); + } + if (actualTypeParam instanceof Class) { + return (Class) actualTypeParam; + } + if (actualTypeParam instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) actualTypeParam).getGenericComponentType(); + if (componentType instanceof ParameterizedType) { + componentType = ((ParameterizedType) componentType).getRawType(); + } + if (componentType instanceof Class) { + return Array.newInstance((Class) componentType, 0).getClass(); + } + } + if (actualTypeParam instanceof TypeVariable) { + // Resolved type parameter points to another type parameter. + TypeVariable v = (TypeVariable) actualTypeParam; + currentClass = thisClass; + if (!(v.getGenericDeclaration() instanceof Class)) { + return Object.class; + } + + parameterizedSuperclass = (Class) v.getGenericDeclaration(); + typeParamName = v.getName(); + if (parameterizedSuperclass.isAssignableFrom(thisClass)) { + continue; + } else { + return Object.class; + } + } + + return fail(thisClass, typeParamName); + } + currentClass = currentClass.getSuperclass(); + if (currentClass == null) { + return fail(thisClass, typeParamName); + } + } + } + + private static Class fail(Class type, String typeParamName) { + throw new IllegalStateException( + "cannot determine the type of the type parameter '" + typeParamName + "': " + type); + } + + public abstract boolean match(Object msg); + + private static final class ReflectiveMatcher extends TypeParameterMatcher { + private final Class type; + + ReflectiveMatcher(Class type) { + this.type = type; + } + + @Override + public boolean match(Object msg) { + return type.isInstance(msg); + } + } + + protected TypeParameterMatcher() { } +} diff --git a/common/src/common/net/util/internal/UnpaddedInternalThreadLocalMap.java b/common/src/common/net/util/internal/UnpaddedInternalThreadLocalMap.java new file mode 100644 index 0000000..689202d --- /dev/null +++ b/common/src/common/net/util/internal/UnpaddedInternalThreadLocalMap.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The internal data structure that stores the thread-local variables for Netty and all {@link FastThreadLocal}s. + * Note that this class is for internal use only and is subject to change at any time. Use {@link FastThreadLocal} + * unless you know what you are doing. + */ +class UnpaddedInternalThreadLocalMap { + + static ThreadLocal slowThreadLocalMap; + static final AtomicInteger nextIndex = new AtomicInteger(); + + /** Used by {@link FastThreadLocal} */ + Object[] indexedVariables; + + // Core thread-locals + int futureListenerStackDepth; + int localChannelReaderStackDepth; + Map, Boolean> handlerSharableCache; + IntegerHolder counterHashCode; + ThreadLocalRandom random; + Map, TypeParameterMatcher> typeParameterMatcherGetCache; + Map, Map> typeParameterMatcherFindCache; + + // String-related thread-locals + StringBuilder stringBuilder; + Map charsetEncoderCache; + Map charsetDecoderCache; + + UnpaddedInternalThreadLocalMap(Object[] indexedVariables) { + this.indexedVariables = indexedVariables; + } +} diff --git a/common/src/common/net/util/internal/UnsafeAtomicIntegerFieldUpdater.java b/common/src/common/net/util/internal/UnsafeAtomicIntegerFieldUpdater.java new file mode 100644 index 0000000..69e3a37 --- /dev/null +++ b/common/src/common/net/util/internal/UnsafeAtomicIntegerFieldUpdater.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import sun.misc.Unsafe; + +final class UnsafeAtomicIntegerFieldUpdater extends AtomicIntegerFieldUpdater { + private final long offset; + private final Unsafe unsafe; + + UnsafeAtomicIntegerFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) throws NoSuchFieldException { + Field field = tClass.getDeclaredField(fieldName); + if (!Modifier.isVolatile(field.getModifiers())) { + throw new IllegalArgumentException("Must be volatile"); + } + this.unsafe = unsafe; + offset = unsafe.objectFieldOffset(field); + } + + @Override + public boolean compareAndSet(T obj, int expect, int update) { + return unsafe.compareAndSwapInt(obj, offset, expect, update); + } + + @Override + public boolean weakCompareAndSet(T obj, int expect, int update) { + return unsafe.compareAndSwapInt(obj, offset, expect, update); + } + + @Override + public void set(T obj, int newValue) { + unsafe.putIntVolatile(obj, offset, newValue); + } + + @Override + public void lazySet(T obj, int newValue) { + unsafe.putOrderedInt(obj, offset, newValue); + } + + @Override + public int get(T obj) { + return unsafe.getIntVolatile(obj, offset); + } +} diff --git a/common/src/common/net/util/internal/UnsafeAtomicLongFieldUpdater.java b/common/src/common/net/util/internal/UnsafeAtomicLongFieldUpdater.java new file mode 100644 index 0000000..ad5b0f8 --- /dev/null +++ b/common/src/common/net/util/internal/UnsafeAtomicLongFieldUpdater.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import sun.misc.Unsafe; + +final class UnsafeAtomicLongFieldUpdater extends AtomicLongFieldUpdater { + private final long offset; + private final Unsafe unsafe; + + UnsafeAtomicLongFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) throws NoSuchFieldException { + Field field = tClass.getDeclaredField(fieldName); + if (!Modifier.isVolatile(field.getModifiers())) { + throw new IllegalArgumentException("Must be volatile"); + } + this.unsafe = unsafe; + offset = unsafe.objectFieldOffset(field); + } + + @Override + public boolean compareAndSet(T obj, long expect, long update) { + return unsafe.compareAndSwapLong(obj, offset, expect, update); + } + + @Override + public boolean weakCompareAndSet(T obj, long expect, long update) { + return unsafe.compareAndSwapLong(obj, offset, expect, update); + } + + @Override + public void set(T obj, long newValue) { + unsafe.putLongVolatile(obj, offset, newValue); + } + + @Override + public void lazySet(T obj, long newValue) { + unsafe.putOrderedLong(obj, offset, newValue); + } + + @Override + public long get(T obj) { + return unsafe.getLongVolatile(obj, offset); + } +} diff --git a/common/src/common/net/util/internal/UnsafeAtomicReferenceFieldUpdater.java b/common/src/common/net/util/internal/UnsafeAtomicReferenceFieldUpdater.java new file mode 100644 index 0000000..66ddd3d --- /dev/null +++ b/common/src/common/net/util/internal/UnsafeAtomicReferenceFieldUpdater.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 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 common.net.util.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import sun.misc.Unsafe; + +final class UnsafeAtomicReferenceFieldUpdater extends AtomicReferenceFieldUpdater { + private final long offset; + private final Unsafe unsafe; + + UnsafeAtomicReferenceFieldUpdater(Unsafe unsafe, Class tClass, String fieldName) throws NoSuchFieldException { + Field field = tClass.getDeclaredField(fieldName); + if (!Modifier.isVolatile(field.getModifiers())) { + throw new IllegalArgumentException("Must be volatile"); + } + this.unsafe = unsafe; + offset = unsafe.objectFieldOffset(field); + } + + @Override + public boolean compareAndSet(U obj, M expect, M update) { + return unsafe.compareAndSwapObject(obj, offset, expect, update); + } + + @Override + public boolean weakCompareAndSet(U obj, M expect, M update) { + return unsafe.compareAndSwapObject(obj, offset, expect, update); + } + + @Override + public void set(U obj, M newValue) { + unsafe.putObjectVolatile(obj, offset, newValue); + } + + @Override + public void lazySet(U obj, M newValue) { + unsafe.putOrderedObject(obj, offset, newValue); + } + + + @Override + public M get(U obj) { + return (M) unsafe.getObjectVolatile(obj, offset); + } +} diff --git a/common/src/common/net/util/internal/logging/AbstractInternalLogger.java b/common/src/common/net/util/internal/logging/AbstractInternalLogger.java new file mode 100644 index 0000000..2ebbf8a --- /dev/null +++ b/common/src/common/net/util/internal/logging/AbstractInternalLogger.java @@ -0,0 +1,190 @@ +/* + * 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 common.net.util.internal.logging; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import common.net.util.internal.StringUtil; + +/** + * A skeletal implementation of {@link InternalLogger}. This class implements + * all methods that have a {@link InternalLogLevel} parameter by default to call + * specific logger methods such as {@link #info(String)} or {@link #isInfoEnabled()}. + */ +public abstract class AbstractInternalLogger implements InternalLogger, Serializable { + + private static final long serialVersionUID = -6382972526573193470L; + + private final String name; + + /** + * Creates a new instance. + */ + protected AbstractInternalLogger(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public boolean isEnabled(InternalLogLevel level) { + switch (level) { + case TRACE: + return isTraceEnabled(); + case DEBUG: + return isDebugEnabled(); + case INFO: + return isInfoEnabled(); + case WARN: + return isWarnEnabled(); + case ERROR: + return isErrorEnabled(); + default: + throw new Error(); + } + } + + @Override + public void log(InternalLogLevel level, String msg, Throwable cause) { + switch (level) { + case TRACE: + trace(msg, cause); + break; + case DEBUG: + debug(msg, cause); + break; + case INFO: + info(msg, cause); + break; + case WARN: + warn(msg, cause); + break; + case ERROR: + error(msg, cause); + break; + default: + throw new Error(); + } + } + + @Override + public void log(InternalLogLevel level, String msg) { + switch (level) { + case TRACE: + trace(msg); + break; + case DEBUG: + debug(msg); + break; + case INFO: + info(msg); + break; + case WARN: + warn(msg); + break; + case ERROR: + error(msg); + break; + default: + throw new Error(); + } + } + + @Override + public void log(InternalLogLevel level, String format, Object arg) { + switch (level) { + case TRACE: + trace(format, arg); + break; + case DEBUG: + debug(format, arg); + break; + case INFO: + info(format, arg); + break; + case WARN: + warn(format, arg); + break; + case ERROR: + error(format, arg); + break; + default: + throw new Error(); + } + } + + @Override + public void log(InternalLogLevel level, String format, Object argA, Object argB) { + switch (level) { + case TRACE: + trace(format, argA, argB); + break; + case DEBUG: + debug(format, argA, argB); + break; + case INFO: + info(format, argA, argB); + break; + case WARN: + warn(format, argA, argB); + break; + case ERROR: + error(format, argA, argB); + break; + default: + throw new Error(); + } + } + + @Override + public void log(InternalLogLevel level, String format, Object... arguments) { + switch (level) { + case TRACE: + trace(format, arguments); + break; + case DEBUG: + debug(format, arguments); + break; + case INFO: + info(format, arguments); + break; + case WARN: + warn(format, arguments); + break; + case ERROR: + error(format, arguments); + break; + default: + throw new Error(); + } + } + + protected Object readResolve() throws ObjectStreamException { + return InternalLoggerFactory.getInstance(name()); + } + + @Override + public String toString() { + return StringUtil.simpleClassName(this) + '(' + name() + ')'; + } +} diff --git a/common/src/common/net/util/internal/logging/FormattingTuple.java b/common/src/common/net/util/internal/logging/FormattingTuple.java new file mode 100644 index 0000000..c87a8e7 --- /dev/null +++ b/common/src/common/net/util/internal/logging/FormattingTuple.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013 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 (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package common.net.util.internal.logging; + +/** + * Holds the results of formatting done by {@link MessageFormatter}. + */ +class FormattingTuple { + + static final FormattingTuple NULL = new FormattingTuple(null); + + private final String message; + private final Throwable throwable; + private final Object[] argArray; + + FormattingTuple(String message) { + this(message, null, null); + } + + FormattingTuple(String message, Object[] argArray, Throwable throwable) { + this.message = message; + this.throwable = throwable; + if (throwable == null) { + this.argArray = argArray; + } else { + this.argArray = trimmedCopy(argArray); + } + } + + static Object[] trimmedCopy(Object[] argArray) { + if (argArray == null || argArray.length == 0) { + throw new IllegalStateException("non-sensical empty or null argument array"); + } + final int trimemdLen = argArray.length - 1; + Object[] trimmed = new Object[trimemdLen]; + System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); + return trimmed; + } + + public String getMessage() { + return message; + } + + public Object[] getArgArray() { + return argArray; + } + + public Throwable getThrowable() { + return throwable; + } +} diff --git a/common/src/common/net/util/internal/logging/InternalLogLevel.java b/common/src/common/net/util/internal/logging/InternalLogLevel.java new file mode 100644 index 0000000..108c2f5 --- /dev/null +++ b/common/src/common/net/util/internal/logging/InternalLogLevel.java @@ -0,0 +1,42 @@ +/* + * 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 common.net.util.internal.logging; + +/** + * The log level that {@link InternalLogger} can log at. + */ +public enum InternalLogLevel { + /** + * 'TRACE' log level. + */ + TRACE, + /** + * 'DEBUG' log level. + */ + DEBUG, + /** + * 'INFO' log level. + */ + INFO, + /** + * 'WARN' log level. + */ + WARN, + /** + * 'ERROR' log level. + */ + ERROR +} diff --git a/common/src/common/net/util/internal/logging/InternalLogger.java b/common/src/common/net/util/internal/logging/InternalLogger.java new file mode 100644 index 0000000..1c2b953 --- /dev/null +++ b/common/src/common/net/util/internal/logging/InternalLogger.java @@ -0,0 +1,444 @@ +/* + * 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 (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package common.net.util.internal.logging; + +/** + * Internal-use-only logger used by Netty. DO NOT + * access this class outside of Netty. + */ +public interface InternalLogger { + + /** + * Return the name of this {@link InternalLogger} instance. + * + * @return name of this logger instance + */ + String name(); + + /** + * Is the logger instance enabled for the TRACE level? + * + * @return True if this Logger is enabled for the TRACE level, + * false otherwise. + */ + boolean isTraceEnabled(); + + /** + * Log a message at the TRACE level. + * + * @param msg the message string to be logged + */ + void trace(String msg); + + /** + * Log a message at the TRACE level according to the specified format + * and argument. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the TRACE level.

+ * + * @param format the format string + * @param arg the argument + */ + void trace(String format, Object arg); + + /** + * Log a message at the TRACE level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the TRACE level.

+ * + * @param format the format string + * @param argA the first argument + * @param argB the second argument + */ + void trace(String format, Object argA, Object argB); + + /** + * Log a message at the TRACE level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the TRACE level. However, this variant incurs the hidden + * (and relatively small) cost of creating an {@code Object[]} before invoking the method, + * even if this logger is disabled for TRACE. The variants taking {@link #trace(String, Object) one} and + * {@link #trace(String, Object, Object) two} arguments exist solely in order to avoid this hidden cost.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void trace(String format, Object... arguments); + + /** + * Log an exception (throwable) at the TRACE level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void trace(String msg, Throwable t); + + /** + * Is the logger instance enabled for the DEBUG level? + * + * @return True if this Logger is enabled for the DEBUG level, + * false otherwise. + */ + boolean isDebugEnabled(); + + /** + * Log a message at the DEBUG level. + * + * @param msg the message string to be logged + */ + void debug(String msg); + + /** + * Log a message at the DEBUG level according to the specified format + * and argument. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the DEBUG level.

+ * + * @param format the format string + * @param arg the argument + */ + void debug(String format, Object arg); + + /** + * Log a message at the DEBUG level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the DEBUG level.

+ * + * @param format the format string + * @param argA the first argument + * @param argB the second argument + */ + void debug(String format, Object argA, Object argB); + + /** + * Log a message at the DEBUG level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the DEBUG level. However, this variant incurs the hidden + * (and relatively small) cost of creating an {@code Object[]} before invoking the method, + * even if this logger is disabled for DEBUG. The variants taking + * {@link #debug(String, Object) one} and {@link #debug(String, Object, Object) two} + * arguments exist solely in order to avoid this hidden cost.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void debug(String format, Object... arguments); + + /** + * Log an exception (throwable) at the DEBUG level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void debug(String msg, Throwable t); + + /** + * Is the logger instance enabled for the INFO level? + * + * @return True if this Logger is enabled for the INFO level, + * false otherwise. + */ + boolean isInfoEnabled(); + + /** + * Log a message at the INFO level. + * + * @param msg the message string to be logged + */ + void info(String msg); + + /** + * Log a message at the INFO level according to the specified format + * and argument. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the INFO level.

+ * + * @param format the format string + * @param arg the argument + */ + void info(String format, Object arg); + + /** + * Log a message at the INFO level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the INFO level.

+ * + * @param format the format string + * @param argA the first argument + * @param argB the second argument + */ + void info(String format, Object argA, Object argB); + + /** + * Log a message at the INFO level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the INFO level. However, this variant incurs the hidden + * (and relatively small) cost of creating an {@code Object[]} before invoking the method, + * even if this logger is disabled for INFO. The variants taking + * {@link #info(String, Object) one} and {@link #info(String, Object, Object) two} + * arguments exist solely in order to avoid this hidden cost.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void info(String format, Object... arguments); + + /** + * Log an exception (throwable) at the INFO level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void info(String msg, Throwable t); + + /** + * Is the logger instance enabled for the WARN level? + * + * @return True if this Logger is enabled for the WARN level, + * false otherwise. + */ + boolean isWarnEnabled(); + + /** + * Log a message at the WARN level. + * + * @param msg the message string to be logged + */ + void warn(String msg); + + /** + * Log a message at the WARN level according to the specified format + * and argument. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the WARN level.

+ * + * @param format the format string + * @param arg the argument + */ + void warn(String format, Object arg); + + /** + * Log a message at the WARN level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the WARN level. However, this variant incurs the hidden + * (and relatively small) cost of creating an {@code Object[]} before invoking the method, + * even if this logger is disabled for WARN. The variants taking + * {@link #warn(String, Object) one} and {@link #warn(String, Object, Object) two} + * arguments exist solely in order to avoid this hidden cost.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void warn(String format, Object... arguments); + + /** + * Log a message at the WARN level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the WARN level.

+ * + * @param format the format string + * @param argA the first argument + * @param argB the second argument + */ + void warn(String format, Object argA, Object argB); + + /** + * Log an exception (throwable) at the WARN level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void warn(String msg, Throwable t); + + /** + * Is the logger instance enabled for the ERROR level? + * + * @return True if this Logger is enabled for the ERROR level, + * false otherwise. + */ + boolean isErrorEnabled(); + + /** + * Log a message at the ERROR level. + * + * @param msg the message string to be logged + */ + void error(String msg); + + /** + * Log a message at the ERROR level according to the specified format + * and argument. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the ERROR level.

+ * + * @param format the format string + * @param arg the argument + */ + void error(String format, Object arg); + + /** + * Log a message at the ERROR level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the ERROR level.

+ * + * @param format the format string + * @param argA the first argument + * @param argB the second argument + */ + void error(String format, Object argA, Object argB); + + /** + * Log a message at the ERROR level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the ERROR level. However, this variant incurs the hidden + * (and relatively small) cost of creating an {@code Object[]} before invoking the method, + * even if this logger is disabled for ERROR. The variants taking + * {@link #error(String, Object) one} and {@link #error(String, Object, Object) two} + * arguments exist solely in order to avoid this hidden cost.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void error(String format, Object... arguments); + + /** + * Log an exception (throwable) at the ERROR level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void error(String msg, Throwable t); + + /** + * Is the logger instance enabled for the specified {@code level}? + * + * @return True if this Logger is enabled for the specified {@code level}, + * false otherwise. + */ + boolean isEnabled(InternalLogLevel level); + + /** + * Log a message at the specified {@code level}. + * + * @param msg the message string to be logged + */ + void log(InternalLogLevel level, String msg); + + /** + * Log a message at the specified {@code level} according to the specified format + * and argument. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the specified {@code level}.

+ * + * @param format the format string + * @param arg the argument + */ + void log(InternalLogLevel level, String format, Object arg); + + /** + * Log a message at the specified {@code level} according to the specified format + * and arguments. + *

+ *

This form avoids superfluous object creation when the logger + * is disabled for the specified {@code level}.

+ * + * @param format the format string + * @param argA the first argument + * @param argB the second argument + */ + void log(InternalLogLevel level, String format, Object argA, Object argB); + + /** + * Log a message at the specified {@code level} according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the specified {@code level}. However, this variant incurs the hidden + * (and relatively small) cost of creating an {@code Object[]} before invoking the method, + * even if this logger is disabled for the specified {@code level}. The variants taking + * {@link #log(InternalLogLevel, String, Object) one} and + * {@link #log(InternalLogLevel, String, Object, Object) two} arguments exist solely + * in order to avoid this hidden cost.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void log(InternalLogLevel level, String format, Object... arguments); + + /** + * Log an exception (throwable) at the specified {@code level} with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void log(InternalLogLevel level, String msg, Throwable t); +} diff --git a/common/src/common/net/util/internal/logging/InternalLoggerFactory.java b/common/src/common/net/util/internal/logging/InternalLoggerFactory.java new file mode 100644 index 0000000..e6d10ff --- /dev/null +++ b/common/src/common/net/util/internal/logging/InternalLoggerFactory.java @@ -0,0 +1,91 @@ +/* + * 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 common.net.util.internal.logging; + +/** + * Creates an {@link InternalLogger} or changes the default factory + * implementation. This factory allows you to choose what logging framework + * Netty should use. The default factory is {@link Slf4JLoggerFactory}. If SLF4J + * is not available, {@link Log4JLoggerFactory} is used. If Log4J is not available, + * {@link JdkLoggerFactory} is used. You can change it to your preferred + * logging framework before other Netty classes are loaded: + *
+ * {@link InternalLoggerFactory}.setDefaultFactory(new {@link Log4JLoggerFactory}());
+ * 
+ * Please note that the new default factory is effective only for the classes + * which were loaded after the default factory is changed. Therefore, + * {@link #setDefaultFactory(InternalLoggerFactory)} should be called as early + * as possible and shouldn't be called more than once. + */ +public abstract class InternalLoggerFactory { + private static volatile InternalLoggerFactory defaultFactory = + newDefaultFactory(InternalLoggerFactory.class.getName()); + +// + private static InternalLoggerFactory newDefaultFactory(String name) { + InternalLoggerFactory f; +// try { +// f = new Slf4JLoggerFactory(true); +// f.newInstance(name).debug("Using SLF4J as the default logging framework"); +// } catch (Throwable t1) { +// try { +// f = new Log4JLoggerFactory(); +// f.newInstance(name).debug("Using Log4J as the default logging framework"); +// } catch (Throwable t2) { + f = new JdkLoggerFactory(); + f.newInstance(name).debug("Using java.util.logging as the default logging framework"); +// } +// } + return f; + } + + /** + * Returns the default factory. The initial default factory is + * {@link JdkLoggerFactory}. + */ + public static InternalLoggerFactory getDefaultFactory() { + return defaultFactory; + } + + /** + * Changes the default factory. + */ + public static void setDefaultFactory(InternalLoggerFactory defaultFactory) { + if (defaultFactory == null) { + throw new NullPointerException("defaultFactory"); + } + InternalLoggerFactory.defaultFactory = defaultFactory; + } + + /** + * Creates a new logger instance with the name of the specified class. + */ + public static InternalLogger getInstance(Class clazz) { + return getInstance(clazz.getName()); + } + + /** + * Creates a new logger instance with the specified name. + */ + public static InternalLogger getInstance(String name) { + return getDefaultFactory().newInstance(name); + } + + /** + * Creates a new logger instance with the specified name. + */ + protected abstract InternalLogger newInstance(String name); +} diff --git a/common/src/common/net/util/internal/logging/JdkLogger.java b/common/src/common/net/util/internal/logging/JdkLogger.java new file mode 100644 index 0000000..a0e69f6 --- /dev/null +++ b/common/src/common/net/util/internal/logging/JdkLogger.java @@ -0,0 +1,647 @@ +/* + * 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 (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package common.net.util.internal.logging; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * java.util.logging + * logger. + */ +class JdkLogger extends AbstractInternalLogger { + + private static final long serialVersionUID = -1767272577989225979L; + + final transient Logger logger; + + JdkLogger(Logger logger) { + super(logger.getName()); + this.logger = logger; + } + + /** + * Is this logger instance enabled for the FINEST level? + * + * @return True if this Logger is enabled for level FINEST, false otherwise. + */ + @Override + public boolean isTraceEnabled() { + return logger.isLoggable(Level.FINEST); + } + + /** + * Log a message object at level FINEST. + * + * @param msg + * - the message object to be logged + */ + @Override + public void trace(String msg) { + if (logger.isLoggable(Level.FINEST)) { + log(SELF, Level.FINEST, msg, null); + } + } + + /** + * Log a message at level FINEST according to the specified format and + * argument. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for level FINEST. + *

+ * + * @param format + * the format string + * @param arg + * the argument + */ + @Override + public void trace(String format, Object arg) { + if (logger.isLoggable(Level.FINEST)) { + FormattingTuple ft = MessageFormatter.format(format, arg); + log(SELF, Level.FINEST, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level FINEST according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the FINEST level. + *

+ * + * @param format + * the format string + * @param argA + * the first argument + * @param argB + * the second argument + */ + @Override + public void trace(String format, Object argA, Object argB) { + if (logger.isLoggable(Level.FINEST)) { + FormattingTuple ft = MessageFormatter.format(format, argA, argB); + log(SELF, Level.FINEST, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level FINEST according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the FINEST level. + *

+ * + * @param format + * the format string + * @param argArray + * an array of arguments + */ + @Override + public void trace(String format, Object... argArray) { + if (logger.isLoggable(Level.FINEST)) { + FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); + log(SELF, Level.FINEST, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log an exception (throwable) at level FINEST with an accompanying message. + * + * @param msg + * the message accompanying the exception + * @param t + * the exception (throwable) to log + */ + @Override + public void trace(String msg, Throwable t) { + if (logger.isLoggable(Level.FINEST)) { + log(SELF, Level.FINEST, msg, t); + } + } + + /** + * Is this logger instance enabled for the FINE level? + * + * @return True if this Logger is enabled for level FINE, false otherwise. + */ + @Override + public boolean isDebugEnabled() { + return logger.isLoggable(Level.FINE); + } + + /** + * Log a message object at level FINE. + * + * @param msg + * - the message object to be logged + */ + @Override + public void debug(String msg) { + if (logger.isLoggable(Level.FINE)) { + log(SELF, Level.FINE, msg, null); + } + } + + /** + * Log a message at level FINE according to the specified format and argument. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for level FINE. + *

+ * + * @param format + * the format string + * @param arg + * the argument + */ + @Override + public void debug(String format, Object arg) { + if (logger.isLoggable(Level.FINE)) { + FormattingTuple ft = MessageFormatter.format(format, arg); + log(SELF, Level.FINE, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level FINE according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the FINE level. + *

+ * + * @param format + * the format string + * @param argA + * the first argument + * @param argB + * the second argument + */ + @Override + public void debug(String format, Object argA, Object argB) { + if (logger.isLoggable(Level.FINE)) { + FormattingTuple ft = MessageFormatter.format(format, argA, argB); + log(SELF, Level.FINE, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level FINE according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the FINE level. + *

+ * + * @param format + * the format string + * @param argArray + * an array of arguments + */ + @Override + public void debug(String format, Object... argArray) { + if (logger.isLoggable(Level.FINE)) { + FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); + log(SELF, Level.FINE, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log an exception (throwable) at level FINE with an accompanying message. + * + * @param msg + * the message accompanying the exception + * @param t + * the exception (throwable) to log + */ + @Override + public void debug(String msg, Throwable t) { + if (logger.isLoggable(Level.FINE)) { + log(SELF, Level.FINE, msg, t); + } + } + + /** + * Is this logger instance enabled for the INFO level? + * + * @return True if this Logger is enabled for the INFO level, false otherwise. + */ + @Override + public boolean isInfoEnabled() { + return logger.isLoggable(Level.INFO); + } + + /** + * Log a message object at the INFO level. + * + * @param msg + * - the message object to be logged + */ + @Override + public void info(String msg) { + if (logger.isLoggable(Level.INFO)) { + log(SELF, Level.INFO, msg, null); + } + } + + /** + * Log a message at level INFO according to the specified format and argument. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the INFO level. + *

+ * + * @param format + * the format string + * @param arg + * the argument + */ + @Override + public void info(String format, Object arg) { + if (logger.isLoggable(Level.INFO)) { + FormattingTuple ft = MessageFormatter.format(format, arg); + log(SELF, Level.INFO, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at the INFO level according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the INFO level. + *

+ * + * @param format + * the format string + * @param argA + * the first argument + * @param argB + * the second argument + */ + @Override + public void info(String format, Object argA, Object argB) { + if (logger.isLoggable(Level.INFO)) { + FormattingTuple ft = MessageFormatter.format(format, argA, argB); + log(SELF, Level.INFO, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level INFO according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the INFO level. + *

+ * + * @param format + * the format string + * @param argArray + * an array of arguments + */ + @Override + public void info(String format, Object... argArray) { + if (logger.isLoggable(Level.INFO)) { + FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); + log(SELF, Level.INFO, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log an exception (throwable) at the INFO level with an accompanying + * message. + * + * @param msg + * the message accompanying the exception + * @param t + * the exception (throwable) to log + */ + @Override + public void info(String msg, Throwable t) { + if (logger.isLoggable(Level.INFO)) { + log(SELF, Level.INFO, msg, t); + } + } + + /** + * Is this logger instance enabled for the WARNING level? + * + * @return True if this Logger is enabled for the WARNING level, false + * otherwise. + */ + @Override + public boolean isWarnEnabled() { + return logger.isLoggable(Level.WARNING); + } + + /** + * Log a message object at the WARNING level. + * + * @param msg + * - the message object to be logged + */ + @Override + public void warn(String msg) { + if (logger.isLoggable(Level.WARNING)) { + log(SELF, Level.WARNING, msg, null); + } + } + + /** + * Log a message at the WARNING level according to the specified format and + * argument. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the WARNING level. + *

+ * + * @param format + * the format string + * @param arg + * the argument + */ + @Override + public void warn(String format, Object arg) { + if (logger.isLoggable(Level.WARNING)) { + FormattingTuple ft = MessageFormatter.format(format, arg); + log(SELF, Level.WARNING, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at the WARNING level according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the WARNING level. + *

+ * + * @param format + * the format string + * @param argA + * the first argument + * @param argB + * the second argument + */ + @Override + public void warn(String format, Object argA, Object argB) { + if (logger.isLoggable(Level.WARNING)) { + FormattingTuple ft = MessageFormatter.format(format, argA, argB); + log(SELF, Level.WARNING, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level WARNING according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the WARNING level. + *

+ * + * @param format + * the format string + * @param argArray + * an array of arguments + */ + @Override + public void warn(String format, Object... argArray) { + if (logger.isLoggable(Level.WARNING)) { + FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); + log(SELF, Level.WARNING, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log an exception (throwable) at the WARNING level with an accompanying + * message. + * + * @param msg + * the message accompanying the exception + * @param t + * the exception (throwable) to log + */ + @Override + public void warn(String msg, Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + log(SELF, Level.WARNING, msg, t); + } + } + + /** + * Is this logger instance enabled for level SEVERE? + * + * @return True if this Logger is enabled for level SEVERE, false otherwise. + */ + @Override + public boolean isErrorEnabled() { + return logger.isLoggable(Level.SEVERE); + } + + /** + * Log a message object at the SEVERE level. + * + * @param msg + * - the message object to be logged + */ + @Override + public void error(String msg) { + if (logger.isLoggable(Level.SEVERE)) { + log(SELF, Level.SEVERE, msg, null); + } + } + + /** + * Log a message at the SEVERE level according to the specified format and + * argument. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the SEVERE level. + *

+ * + * @param format + * the format string + * @param arg + * the argument + */ + @Override + public void error(String format, Object arg) { + if (logger.isLoggable(Level.SEVERE)) { + FormattingTuple ft = MessageFormatter.format(format, arg); + log(SELF, Level.SEVERE, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at the SEVERE level according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the SEVERE level. + *

+ * + * @param format + * the format string + * @param argA + * the first argument + * @param argB + * the second argument + */ + @Override + public void error(String format, Object argA, Object argB) { + if (logger.isLoggable(Level.SEVERE)) { + FormattingTuple ft = MessageFormatter.format(format, argA, argB); + log(SELF, Level.SEVERE, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log a message at level SEVERE according to the specified format and + * arguments. + * + *

+ * This form avoids superfluous object creation when the logger is disabled + * for the SEVERE level. + *

+ * + * @param format + * the format string + * @param arguments + * an array of arguments + */ + @Override + public void error(String format, Object... arguments) { + if (logger.isLoggable(Level.SEVERE)) { + FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments); + log(SELF, Level.SEVERE, ft.getMessage(), ft.getThrowable()); + } + } + + /** + * Log an exception (throwable) at the SEVERE level with an accompanying + * message. + * + * @param msg + * the message accompanying the exception + * @param t + * the exception (throwable) to log + */ + @Override + public void error(String msg, Throwable t) { + if (logger.isLoggable(Level.SEVERE)) { + log(SELF, Level.SEVERE, msg, t); + } + } + + /** + * Log the message at the specified level with the specified throwable if any. + * This method creates a LogRecord and fills in caller date before calling + * this instance's JDK14 logger. + * + * See bug report #13 for more details. + */ + private void log(String callerFQCN, Level level, String msg, Throwable t) { + // millis and thread are filled by the constructor + LogRecord record = new LogRecord(level, msg); + record.setLoggerName(name()); + record.setThrown(t); + fillCallerData(callerFQCN, record); + logger.log(record); + } + + static final String SELF = JdkLogger.class.getName(); + static final String SUPER = AbstractInternalLogger.class.getName(); + + /** + * Fill in caller data if possible. + * + * @param record + * The record to update + */ + private static void fillCallerData(String callerFQCN, LogRecord record) { + StackTraceElement[] steArray = new Throwable().getStackTrace(); + + int selfIndex = -1; + for (int i = 0; i < steArray.length; i++) { + final String className = steArray[i].getClassName(); + if (className.equals(callerFQCN) || className.equals(SUPER)) { + selfIndex = i; + break; + } + } + + int found = -1; + for (int i = selfIndex + 1; i < steArray.length; i++) { + final String className = steArray[i].getClassName(); + if (!(className.equals(callerFQCN) || className.equals(SUPER))) { + found = i; + break; + } + } + + if (found != -1) { + StackTraceElement ste = steArray[found]; + // setting the class name has the side effect of setting + // the needToInferCaller variable to false. + record.setSourceClassName(ste.getClassName()); + record.setSourceMethodName(ste.getMethodName()); + } + } +} diff --git a/common/src/common/net/util/internal/logging/JdkLoggerFactory.java b/common/src/common/net/util/internal/logging/JdkLoggerFactory.java new file mode 100644 index 0000000..a1e88dd --- /dev/null +++ b/common/src/common/net/util/internal/logging/JdkLoggerFactory.java @@ -0,0 +1,32 @@ +/* + * 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 common.net.util.internal.logging; + + +import java.util.logging.Logger; + +/** + * Logger factory which creates a + * java.util.logging + * logger. + */ +public class JdkLoggerFactory extends InternalLoggerFactory { + + @Override + public InternalLogger newInstance(String name) { + return new JdkLogger(Logger.getLogger(name)); + } +} diff --git a/common/src/common/net/util/internal/logging/MessageFormatter.java b/common/src/common/net/util/internal/logging/MessageFormatter.java new file mode 100644 index 0000000..a1e956a --- /dev/null +++ b/common/src/common/net/util/internal/logging/MessageFormatter.java @@ -0,0 +1,427 @@ +/* + * Copyright 2013 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 (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package common.net.util.internal.logging; + +import java.util.HashMap; +import java.util.Map; + +// contributors: lizongbo: proposed special treatment of array parameter values +// Joern Huxhorn: pointed out double[] omission, suggested deep array copy + +/** + * Formats messages according to very simple substitution rules. Substitutions + * can be made 1, 2 or more arguments. + *

+ *

+ * For example, + *

+ *

+ * MessageFormatter.format("Hi {}.", "there")
+ * 
+ *

+ * will return the string "Hi there.". + *

+ * The {} pair is called the formatting anchor. It serves to designate + * the location where arguments need to be substituted within the message + * pattern. + *

+ * In case your message contains the '{' or the '}' character, you do not have + * to do anything special unless the '}' character immediately follows '{'. For + * example, + *

+ *

+ * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2");
+ * 
+ *

+ * will return the string "Set {1,2,3} is not equal to 1,2.". + *

+ *

+ * If for whatever reason you need to place the string "{}" in the message + * without its formatting anchor meaning, then you need to escape the + * '{' character with '\', that is the backslash character. Only the '{' + * character should be escaped. There is no need to escape the '}' character. + * For example, + *

+ *

+ * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2");
+ * 
+ *

+ * will return the string "Set {} is not equal to 1,2.". + *

+ *

+ * The escaping behavior just described can be overridden by escaping the escape + * character '\'. Calling + *

+ *

+ * MessageFormatter.format("File name is C:\\\\{}.", "file.zip");
+ * 
+ *

+ * will return the string "File name is C:\file.zip". + *

+ *

+ * The formatting conventions are different than those of {@link MessageFormat} + * which ships with the Java platform. This is justified by the fact that + * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}. + * This local performance difference is both measurable and significant in the + * larger context of the complete logging processing chain. + *

+ *

+ * See also {@link #format(String, Object)}, + * {@link #format(String, Object, Object)} and + * {@link #arrayFormat(String, Object[])} methods for more details. + */ +final class MessageFormatter { + static final char DELIM_START = '{'; + static final char DELIM_STOP = '}'; + static final String DELIM_STR = "{}"; + private static final char ESCAPE_CHAR = '\\'; + + /** + * Performs single argument substitution for the 'messagePattern' passed as + * parameter. + *

+ * For example, + *

+ *

+     * MessageFormatter.format("Hi {}.", "there");
+     * 
+ *

+ * will return the string "Hi there.". + *

+ * + * @param messagePattern The message pattern which will be parsed and formatted + * @param arg The argument to be substituted in place of the formatting anchor + * @return The formatted message + */ + static FormattingTuple format(String messagePattern, Object arg) { + return arrayFormat(messagePattern, new Object[]{arg}); + } + + /** + * Performs a two argument substitution for the 'messagePattern' passed as + * parameter. + *

+ * For example, + *

+ *

+     * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
+     * 
+ *

+ * will return the string "Hi Alice. My name is Bob.". + * + * @param messagePattern The message pattern which will be parsed and formatted + * @param argA The argument to be substituted in place of the first formatting + * anchor + * @param argB The argument to be substituted in place of the second formatting + * anchor + * @return The formatted message + */ + static FormattingTuple format(final String messagePattern, + Object argA, Object argB) { + return arrayFormat(messagePattern, new Object[]{argA, argB}); + } + + static Throwable getThrowableCandidate(Object[] argArray) { + if (argArray == null || argArray.length == 0) { + return null; + } + + final Object lastEntry = argArray[argArray.length - 1]; + if (lastEntry instanceof Throwable) { + return (Throwable) lastEntry; + } + return null; + } + + /** + * Same principle as the {@link #format(String, Object)} and + * {@link #format(String, Object, Object)} methods except that any number of + * arguments can be passed in an array. + * + * @param messagePattern The message pattern which will be parsed and formatted + * @param argArray An array of arguments to be substituted in place of formatting + * anchors + * @return The formatted message + */ + static FormattingTuple arrayFormat(final String messagePattern, + final Object[] argArray) { + + Throwable throwableCandidate = getThrowableCandidate(argArray); + + if (messagePattern == null) { + return new FormattingTuple(null, argArray, throwableCandidate); + } + + if (argArray == null) { + return new FormattingTuple(messagePattern); + } + + int i = 0; + int j; + StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50); + + int L; + for (L = 0; L < argArray.length; L++) { + + j = messagePattern.indexOf(DELIM_STR, i); + + if (j == -1) { + // no more variables + if (i == 0) { // this is a simple string + return new FormattingTuple(messagePattern, argArray, + throwableCandidate); + } else { // add the tail string which contains no variables and return + // the result. + sbuf.append(messagePattern.substring(i, messagePattern.length())); + return new FormattingTuple(sbuf.toString(), argArray, + throwableCandidate); + } + } else { + if (isEscapedDelimeter(messagePattern, j)) { + if (!isDoubleEscaped(messagePattern, j)) { + L--; // DELIM_START was escaped, thus should not be incremented + sbuf.append(messagePattern.substring(i, j - 1)); + sbuf.append(DELIM_START); + i = j + 1; + } else { + // The escape character preceding the delimiter start is + // itself escaped: "abc x:\\{}" + // we have to consume one backward slash + sbuf.append(messagePattern.substring(i, j - 1)); + deeplyAppendParameter(sbuf, argArray[L], new HashMap()); + i = j + 2; + } + } else { + // normal case + sbuf.append(messagePattern.substring(i, j)); + deeplyAppendParameter(sbuf, argArray[L], new HashMap()); + i = j + 2; + } + } + } + // append the characters following the last {} pair. + sbuf.append(messagePattern.substring(i, messagePattern.length())); + if (L < argArray.length - 1) { + return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); + } else { + return new FormattingTuple(sbuf.toString(), argArray, null); + } + } + + static boolean isEscapedDelimeter(String messagePattern, + int delimeterStartIndex) { + + if (delimeterStartIndex == 0) { + return false; + } + return messagePattern.charAt(delimeterStartIndex - 1) == ESCAPE_CHAR; + } + + static boolean isDoubleEscaped(String messagePattern, + int delimeterStartIndex) { + return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR; + } + + // special treatment of array values was suggested by 'lizongbo' + private static void deeplyAppendParameter(StringBuffer sbuf, Object o, + Map seenMap) { + if (o == null) { + sbuf.append("null"); + return; + } + if (!o.getClass().isArray()) { + safeObjectAppend(sbuf, o); + } else { + // check for primitive array types because they + // unfortunately cannot be cast to Object[] + if (o instanceof boolean[]) { + booleanArrayAppend(sbuf, (boolean[]) o); + } else if (o instanceof byte[]) { + byteArrayAppend(sbuf, (byte[]) o); + } else if (o instanceof char[]) { + charArrayAppend(sbuf, (char[]) o); + } else if (o instanceof short[]) { + shortArrayAppend(sbuf, (short[]) o); + } else if (o instanceof int[]) { + intArrayAppend(sbuf, (int[]) o); + } else if (o instanceof long[]) { + longArrayAppend(sbuf, (long[]) o); + } else if (o instanceof float[]) { + floatArrayAppend(sbuf, (float[]) o); + } else if (o instanceof double[]) { + doubleArrayAppend(sbuf, (double[]) o); + } else { + objectArrayAppend(sbuf, (Object[]) o, seenMap); + } + } + } + + private static void safeObjectAppend(StringBuffer sbuf, Object o) { + try { + String oAsString = o.toString(); + sbuf.append(oAsString); + } catch (Throwable t) { + System.err + .println("SLF4J: Failed toString() invocation on an object of type [" + + o.getClass().getName() + ']'); + t.printStackTrace(); + sbuf.append("[FAILED toString()]"); + } + } + + private static void objectArrayAppend(StringBuffer sbuf, Object[] a, + Map seenMap) { + sbuf.append('['); + if (!seenMap.containsKey(a)) { + seenMap.put(a, null); + final int len = a.length; + for (int i = 0; i < len; i++) { + deeplyAppendParameter(sbuf, a[i], seenMap); + if (i != len - 1) { + sbuf.append(", "); + } + } + // allow repeats in siblings + seenMap.remove(a); + } else { + sbuf.append("..."); + } + sbuf.append(']'); + } + + private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void byteArrayAppend(StringBuffer sbuf, byte[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void charArrayAppend(StringBuffer sbuf, char[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void shortArrayAppend(StringBuffer sbuf, short[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void intArrayAppend(StringBuffer sbuf, int[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void longArrayAppend(StringBuffer sbuf, long[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void floatArrayAppend(StringBuffer sbuf, float[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void doubleArrayAppend(StringBuffer sbuf, double[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private MessageFormatter() { + } +} diff --git a/common/src/common/network/CompressionDecoder.java b/common/src/common/network/CompressionDecoder.java index e681218..c4b28db 100755 --- a/common/src/common/network/CompressionDecoder.java +++ b/common/src/common/network/CompressionDecoder.java @@ -4,11 +4,11 @@ import java.util.List; import java.util.zip.DataFormatException; import java.util.zip.Inflater; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.DecoderException; +import common.net.buffer.ByteBuf; +import common.net.buffer.Unpooled; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.ByteToMessageDecoder; +import common.net.handler.codec.DecoderException; public class CompressionDecoder extends ByteToMessageDecoder { private final Inflater inflater; diff --git a/common/src/common/network/CompressionEncoder.java b/common/src/common/network/CompressionEncoder.java index 8d975f2..c2cf62f 100755 --- a/common/src/common/network/CompressionEncoder.java +++ b/common/src/common/network/CompressionEncoder.java @@ -2,9 +2,9 @@ package common.network; import java.util.zip.Deflater; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.MessageToByteEncoder; public class CompressionEncoder extends MessageToByteEncoder { private final byte[] buffer = new byte[8192]; diff --git a/common/src/common/network/EncryptionCodec.java b/common/src/common/network/EncryptionCodec.java index 2598ecc..4a6d981 100644 --- a/common/src/common/network/EncryptionCodec.java +++ b/common/src/common/network/EncryptionCodec.java @@ -1,10 +1,11 @@ package common.network; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; import javax.crypto.Cipher; import javax.crypto.ShortBufferException; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; + public class EncryptionCodec { private final Cipher cipher; private byte[] receiveBuf = new byte[0]; diff --git a/common/src/common/network/EncryptionDecoder.java b/common/src/common/network/EncryptionDecoder.java index 244773b..7c4bdb8 100644 --- a/common/src/common/network/EncryptionDecoder.java +++ b/common/src/common/network/EncryptionDecoder.java @@ -1,12 +1,13 @@ package common.network; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; import javax.crypto.Cipher; import javax.crypto.ShortBufferException; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.MessageToMessageDecoder; + public class EncryptionDecoder extends MessageToMessageDecoder { private final EncryptionCodec codec; diff --git a/common/src/common/network/EncryptionEncoder.java b/common/src/common/network/EncryptionEncoder.java index abfa860..522c815 100644 --- a/common/src/common/network/EncryptionEncoder.java +++ b/common/src/common/network/EncryptionEncoder.java @@ -1,11 +1,12 @@ package common.network; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; import javax.crypto.Cipher; import javax.crypto.ShortBufferException; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.MessageToByteEncoder; + public class EncryptionEncoder extends MessageToByteEncoder { private final EncryptionCodec codec; diff --git a/common/src/common/network/NetConnection.java b/common/src/common/network/NetConnection.java index fd9169c..38d2eb2 100755 --- a/common/src/common/network/NetConnection.java +++ b/common/src/common/network/NetConnection.java @@ -11,17 +11,17 @@ import javax.crypto.Cipher; import javax.crypto.SecretKey; import common.log.Log; +import common.net.channel.Channel; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelFutureListener; +import common.net.channel.ChannelHandlerContext; +import common.net.channel.SimpleChannelInboundHandler; +import common.net.handler.timeout.TimeoutException; +import common.net.util.AttributeKey; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; import common.network.NetHandler.ThreadQuickExitException; import common.util.EncryptUtil; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.timeout.TimeoutException; -import io.netty.util.AttributeKey; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; public class NetConnection extends SimpleChannelInboundHandler { private static final boolean DEBUG = System.getProperty("network.debug") != null; diff --git a/common/src/common/network/PacketBuffer.java b/common/src/common/network/PacketBuffer.java index cbc0a3b..e7630ac 100755 --- a/common/src/common/network/PacketBuffer.java +++ b/common/src/common/network/PacketBuffer.java @@ -8,12 +8,12 @@ import common.item.ItemStack; import common.nbt.NBTLoader; import common.nbt.NBTSizeTracker; import common.nbt.NBTTagCompound; +import common.net.buffer.ByteBuf; +import common.net.buffer.ByteBufInputStream; +import common.net.buffer.ByteBufOutputStream; +import common.net.handler.codec.DecoderException; +import common.net.handler.codec.EncoderException; import common.util.BlockPos; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.handler.codec.DecoderException; -import io.netty.handler.codec.EncoderException; public class PacketBuffer { private static final Charset UTF_8 = Charset.forName("UTF-8"); diff --git a/common/src/common/network/PacketDecoder.java b/common/src/common/network/PacketDecoder.java index e7e25bd..121e34d 100755 --- a/common/src/common/network/PacketDecoder.java +++ b/common/src/common/network/PacketDecoder.java @@ -3,9 +3,9 @@ package common.network; import java.io.IOException; import java.util.List; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.ByteToMessageDecoder; public class PacketDecoder extends ByteToMessageDecoder { private final boolean client; diff --git a/common/src/common/network/PacketEncoder.java b/common/src/common/network/PacketEncoder.java index 5a0a628..0f123ef 100755 --- a/common/src/common/network/PacketEncoder.java +++ b/common/src/common/network/PacketEncoder.java @@ -2,9 +2,9 @@ package common.network; import java.io.IOException; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.MessageToByteEncoder; public class PacketEncoder extends MessageToByteEncoder { private final boolean client; diff --git a/common/src/common/network/PacketPrepender.java b/common/src/common/network/PacketPrepender.java index 7a89749..943257c 100755 --- a/common/src/common/network/PacketPrepender.java +++ b/common/src/common/network/PacketPrepender.java @@ -1,8 +1,8 @@ package common.network; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; +import common.net.buffer.ByteBuf; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.MessageToByteEncoder; public class PacketPrepender extends MessageToByteEncoder { protected void encode(ChannelHandlerContext context, ByteBuf buffer, ByteBuf output) throws Exception { diff --git a/common/src/common/network/PacketSplitter.java b/common/src/common/network/PacketSplitter.java index acb73d5..3874408 100755 --- a/common/src/common/network/PacketSplitter.java +++ b/common/src/common/network/PacketSplitter.java @@ -2,11 +2,11 @@ package common.network; import java.util.List; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.CorruptedFrameException; +import common.net.buffer.ByteBuf; +import common.net.buffer.Unpooled; +import common.net.channel.ChannelHandlerContext; +import common.net.handler.codec.ByteToMessageDecoder; +import common.net.handler.codec.CorruptedFrameException; public class PacketSplitter extends ByteToMessageDecoder { protected void decode(ChannelHandlerContext context, ByteBuf buffer, List output) throws Exception { diff --git a/server/src/server/Server.java b/server/src/server/Server.java index 3b70321..5d0b5cb 100755 --- a/server/src/server/Server.java +++ b/server/src/server/Server.java @@ -45,6 +45,19 @@ import common.log.Log; import common.nbt.NBTLoader; import common.nbt.NBTTagCompound; import common.nbt.NBTTagList; +import common.net.bootstrap.ServerBootstrap; +import common.net.channel.Channel; +import common.net.channel.ChannelException; +import common.net.channel.ChannelFuture; +import common.net.channel.ChannelFutureListener; +import common.net.channel.ChannelHandler; +import common.net.channel.ChannelInitializer; +import common.net.channel.ChannelOption; +import common.net.channel.nio.NioEventLoopGroup; +import common.net.channel.socket.nio.NioServerSocketChannel; +import common.net.handler.timeout.ReadTimeoutHandler; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; import common.network.IPlayer; import common.network.IThreadListener; import common.network.NetConnection; @@ -78,19 +91,6 @@ import common.util.Position; import common.util.Util; import common.util.WorldPos; import common.world.World; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelException; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; import server.biome.GenBiome; import server.clipboard.ReorderRegistry; import server.clipboard.RotationRegistry; diff --git a/server/src/server/network/Player.java b/server/src/server/network/Player.java index e8ce73a..f45fafb 100755 --- a/server/src/server/network/Player.java +++ b/server/src/server/network/Player.java @@ -48,6 +48,8 @@ import common.item.ItemStack; import common.log.Log; import common.nbt.NBTTagCompound; import common.nbt.NBTTagList; +import common.net.util.concurrent.Future; +import common.net.util.concurrent.GenericFutureListener; import common.network.IPlayer; import common.network.NetConnection; import common.network.NetHandler; @@ -120,8 +122,6 @@ import common.village.MerchantRecipeList; import common.world.BlockArray; import common.world.State; import common.world.World; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; import server.Server; import server.clipboard.BlockTransform; import server.clipboard.ClipboardBlock;