Skip to content

Commit

Permalink
Ensure ChannelOptions are applied in the same order as configured in …
Browse files Browse the repository at this point in the history
…*Bootstrap (netty#9998)

Motivation:

netty#9458 changed how we handled ChannelOptions internally to use a ConcurrentHashMap. This unfortunally had the side-effect that the ordering may be affected and not stable anymore. Here the problem is that sometimes we do validation based on two different ChannelOptions (for example we validate high and low watermarks against each other). Thus even if the user specified the options in the same order we may fail to configure them.

Modifications:

- Use again a LinkedHashMap to preserve order
- Add unit test

Result:

Apply ChannelOptions in correct and expected order
  • Loading branch information
normanmaurer authored Feb 6, 2020
1 parent 38b5607 commit 56055f4
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 18 deletions.
30 changes: 23 additions & 7 deletions transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.net.SocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand All @@ -59,7 +60,10 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
@SuppressWarnings("deprecation")
private volatile ChannelFactory<? extends C> channelFactory;
private volatile SocketAddress localAddress;
private final Map<ChannelOption<?>, Object> options = new ConcurrentHashMap<ChannelOption<?>, Object>();

// The order in which ChannelOptions are applied is important they may depend on each other for validation
// purposes.
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
private volatile ChannelHandler handler;

Expand All @@ -72,7 +76,9 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
channelFactory = bootstrap.channelFactory;
handler = bootstrap.handler;
localAddress = bootstrap.localAddress;
options.putAll(bootstrap.options);
synchronized (bootstrap.options) {
options.putAll(bootstrap.options);
}
attrs.putAll(bootstrap.attrs);
}

Expand Down Expand Up @@ -166,10 +172,12 @@ public B localAddress(InetAddress inetHost, int inetPort) {
*/
public <T> B option(ChannelOption<T> option, T value) {
ObjectUtil.checkNotNull(option, "option");
if (value == null) {
options.remove(option);
} else {
options.put(option, value);
synchronized (options) {
if (value == null) {
options.remove(option);
} else {
options.put(option, value);
}
}
return self();
}
Expand Down Expand Up @@ -377,6 +385,12 @@ public final EventLoopGroup group() {
*/
public abstract AbstractBootstrapConfig<B, C> config();

final Map.Entry<ChannelOption<?>, Object>[] newOptionsArray() {
synchronized (options) {
return options.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
}

final Map<ChannelOption<?>, Object> options0() {
return options;
}
Expand All @@ -399,7 +413,9 @@ final ChannelHandler handler() {
}

final Map<ChannelOption<?>, Object> options() {
return copiedMap(options);
synchronized (options) {
return copiedMap(options);
}
}

final Map<AttributeKey<?>, Object> attrs() {
Expand Down
2 changes: 1 addition & 1 deletion transport/src/main/java/io/netty/bootstrap/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ void init(Channel channel) {
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());

setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
}

Expand Down
31 changes: 21 additions & 10 deletions transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -45,7 +46,9 @@ public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerCh

private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);

private final Map<ChannelOption<?>, Object> childOptions = new ConcurrentHashMap<ChannelOption<?>, Object>();
// The order in which child ChannelOptions are applied is important they may depend on each other for validation
// purposes.
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
private volatile EventLoopGroup childGroup;
Expand All @@ -57,7 +60,9 @@ private ServerBootstrap(ServerBootstrap bootstrap) {
super(bootstrap);
childGroup = bootstrap.childGroup;
childHandler = bootstrap.childHandler;
childOptions.putAll(bootstrap.childOptions);
synchronized (bootstrap.childOptions) {
childOptions.putAll(bootstrap.childOptions);
}
childAttrs.putAll(bootstrap.childAttrs);
}

Expand Down Expand Up @@ -90,10 +95,12 @@ public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGro
*/
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
ObjectUtil.checkNotNull(childOption, "childOption");
if (value == null) {
childOptions.remove(childOption);
} else {
childOptions.put(childOption, value);
synchronized (childOptions) {
if (value == null) {
childOptions.remove(childOption);
} else {
childOptions.put(childOption, value);
}
}
return this;
}
Expand Down Expand Up @@ -122,15 +129,17 @@ public ServerBootstrap childHandler(ChannelHandler childHandler) {

@Override
void init(Channel channel) {
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

ChannelPipeline p = channel.pipeline();

final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions =
childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

p.addLast(new ChannelInitializer<Channel>() {
Expand Down Expand Up @@ -261,7 +270,9 @@ final ChannelHandler childHandler() {
}

final Map<ChannelOption<?>, Object> childOptions() {
return copiedMap(childOptions);
synchronized (childOptions) {
return copiedMap(childOptions);
}
}

final Map<AttributeKey<?>, Object> childAttrs() {
Expand Down
57 changes: 57 additions & 0 deletions transport/src/test/java/io/netty/bootstrap/BootstrapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@
package io.netty.bootstrap;

import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelConfig;
import io.netty.channel.DefaultEventLoop;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.local.LocalAddress;
Expand All @@ -47,7 +52,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;

import static org.hamcrest.Matchers.*;
Expand Down Expand Up @@ -293,6 +300,56 @@ public Channel newChannel() {
assertThat(connectFuture.channel(), is(not(nullValue())));
}

@Test
public void testChannelOptionOrderPreserve() throws InterruptedException {
final BlockingQueue<ChannelOption<?>> options = new LinkedBlockingQueue<ChannelOption<?>>();
class ChannelConfigValidator extends DefaultChannelConfig {
ChannelConfigValidator(Channel channel) {
super(channel);
}

@Override
public <T> boolean setOption(ChannelOption<T> option, T value) {
options.add(option);
return super.setOption(option, value);
}
}
final CountDownLatch latch = new CountDownLatch(1);
final Bootstrap bootstrap = new Bootstrap()
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
latch.countDown();
}
})
.group(groupA)
.channelFactory(new ChannelFactory<Channel>() {
@Override
public Channel newChannel() {
return new LocalChannel() {
private ChannelConfigValidator config;
@Override
public synchronized ChannelConfig config() {
if (config == null) {
config = new ChannelConfigValidator(this);
}
return config;
}
};
}
})
.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 1)
.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 2);

bootstrap.register().syncUninterruptibly();

latch.await();

// Check the order is the same as what we defined before.
assertSame(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, options.take());
assertSame(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, options.take());
}

private static final class DelayedEventLoopGroup extends DefaultEventLoop {
@Override
public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
Expand Down

0 comments on commit 56055f4

Please sign in to comment.