Improve flexibility of EmbeddedChannel ID

Motivation:

EmbeddedChannelId#hashCode() and equals() do not behave correctly if an
instance is serialized and then deserialized. Additionally,
EmbeddedChannel does not allow use of any other type of ChannelId, and
EmbeddedChannelId is (mostly) a singleton instance. This creates a
problem for unit tests that construct multiple EmbeddedChannels and
expect each channel to have a unique ID.

Modifications:

EmbeddedChannelId is modified so equals() will return true and
hashCode() will return the same value for any instance of the class.
EmbeddedChannel is modified to allow a ChannelId to be specified when
the channel is constructed. Tests added for both changes.

Result:

EmbeddedChannelId now behaves correctly when deserialized, and
EmbeddedChannels can now have unique IDs.
This commit is contained in:
Richard DiCroce 2015-10-09 19:37:37 -04:00 committed by Norman Maurer
parent 30dc1c1fa4
commit b58036aeea
5 changed files with 151 additions and 6 deletions

View File

@ -21,6 +21,7 @@ import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelMetadata;
@ -64,19 +65,39 @@ public class EmbeddedChannel extends AbstractChannel {
private State state;
/**
* Create a new instance with an empty pipeline.
* Create a new instance with an {@link EmbeddedChannelId} and an empty pipeline.
*/
public EmbeddedChannel() {
this(EMPTY_HANDLERS);
}
/**
* Create a new instance with the specified ID and an empty pipeline.
*
* @param channelId the {@link ChannelId} that will be used to identify this channel
*/
public EmbeddedChannel(ChannelId channelId) {
this(channelId, EMPTY_HANDLERS);
}
/**
* Create a new instance with the pipeline initialized with the specified handlers.
*
* @param handlers the @link ChannelHandler}s which will be add in the {@link ChannelPipeline}
* @param handlers the {@link ChannelHandler}s which will be add in the {@link ChannelPipeline}
*/
public EmbeddedChannel(final ChannelHandler... handlers) {
super(null, EmbeddedChannelId.INSTANCE);
this(EmbeddedChannelId.INSTANCE, handlers);
}
/**
* Create a new instance with the channel ID set to the given ID and the pipeline
* initialized with the specified handlers.
*
* @param channelId the {@link ChannelId} that will be used to identify this channel
* @param handlers the {@link ChannelHandler}s which will be add in the {@link ChannelPipeline}
*/
public EmbeddedChannel(ChannelId channelId, final ChannelHandler... handlers) {
super(null, channelId);
if (handlers == null) {
throw new NullPointerException("handlers");

View File

@ -41,7 +41,7 @@ final class EmbeddedChannelId implements ChannelId {
@Override
public int compareTo(ChannelId o) {
if (o == INSTANCE) {
if (o instanceof EmbeddedChannelId) {
return 0;
}
@ -50,12 +50,12 @@ final class EmbeddedChannelId implements ChannelId {
@Override
public int hashCode() {
return super.hashCode();
return 0;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
return obj instanceof EmbeddedChannelId;
}
@Override

View File

@ -0,0 +1,65 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.embedded;
import io.netty.channel.ChannelId;
public class CustomChannelId implements ChannelId {
private static final long serialVersionUID = 1L;
private int id;
public CustomChannelId(int id) {
this.id = id;
}
@Override
public int compareTo(ChannelId o) {
if (o instanceof CustomChannelId) {
return id - ((CustomChannelId) o).id;
}
return -1;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CustomChannelId) {
return id == ((CustomChannelId) obj).id;
}
return false;
}
@Override
public String toString() {
return "CustomChannelId " + id;
}
@Override
public String asShortText() {
return toString();
}
@Override
public String asLongText() {
return toString();
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.embedded;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.junit.Assert;
import org.junit.Test;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelId;
public class EmbeddedChannelIdTest {
@Test
public void testSerialization() throws IOException, ClassNotFoundException {
// test that a deserialized instance works the same as a normal instance (issue #2869)
ChannelId normalInstance = EmbeddedChannelId.INSTANCE;
ByteBufOutputStream buffer = new ByteBufOutputStream(Unpooled.buffer());
ObjectOutputStream outStream = new ObjectOutputStream(buffer);
outStream.writeObject(normalInstance);
outStream.close();
ObjectInputStream inStream = new ObjectInputStream(new ByteBufInputStream(buffer.buffer()));
ChannelId deserializedInstance = (ChannelId) inStream.readObject();
inStream.close();
Assert.assertEquals(normalInstance, deserializedInstance);
Assert.assertEquals(normalInstance.hashCode(), deserializedInstance.hashCode());
Assert.assertEquals(0, normalInstance.compareTo(deserializedInstance));
}
}

View File

@ -20,6 +20,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
@ -136,6 +137,13 @@ public class EmbeddedChannelTest {
Assert.assertNull(channel.readOutbound());
}
@Test
public void testConstructWithChannelId() {
ChannelId channelId = new CustomChannelId(1);
EmbeddedChannel channel = new EmbeddedChannel(channelId);
Assert.assertSame(channelId, channel.id());
}
// See https://github.com/netty/netty/issues/4316.
@Test(timeout = 2000)
public void testFireChannelInactiveAndUnregisteredOnClose() throws InterruptedException {