Skip to content

Commit 0bde1a7

Browse files
committed
Remaining threads (netty task-4-1) fix jooby-project#581
1 parent 7732212 commit 0bde1a7

File tree

4 files changed

+187
-7
lines changed

4 files changed

+187
-7
lines changed

jooby-netty/src/main/java/org/jooby/internal/netty/NettyServer.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import io.netty.util.concurrent.DefaultEventExecutorGroup;
5757
import io.netty.util.concurrent.DefaultThreadFactory;
5858
import io.netty.util.concurrent.EventExecutorGroup;
59+
import javaslang.control.Try;
5960

6061
public class NettyServer implements Server {
6162

@@ -76,6 +77,8 @@ public class NettyServer implements Server {
7677

7778
private HttpHandler dispatcher;
7879

80+
private DefaultEventExecutorGroup executor;
81+
7982
@Inject
8083
public NettyServer(final HttpHandler dispatcher, final Config config) {
8184
this.dispatcher = dispatcher;
@@ -94,7 +97,7 @@ public void start() throws Exception {
9497
}
9598

9699
ThreadFactory threadFactory = new DefaultThreadFactory(conf.getString("netty.threads.Name"));
97-
DefaultEventExecutorGroup executor = new DefaultEventExecutorGroup(
100+
this.executor = new DefaultEventExecutorGroup(
98101
conf.getInt("netty.threads.Max"), threadFactory);
99102

100103
this.ch = bootstrap(executor, null, conf.getInt("application.port"));
@@ -134,9 +137,13 @@ private String host(final String host) {
134137

135138
@Override
136139
public void stop() throws Exception {
137-
bossLoop.shutdownGracefully();
140+
Try.run(() -> executor.shutdownGracefully())
141+
.onFailure(x -> log.debug("executor shutdown resulted in exception", x));
142+
Try.run(() -> bossLoop.shutdownGracefully())
143+
.onFailure(x -> log.debug("event loop shutdown resulted in exception", x));
138144
if (!workerLoop.isShutdown()) {
139-
workerLoop.shutdownGracefully();
145+
Try.run(() -> workerLoop.shutdownGracefully())
146+
.onFailure(x -> log.debug("worker event loop shutdown resulted in exception", x));
140147
}
141148
}
142149

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package org.jooby.internal.netty;
2+
3+
import static org.easymock.EasyMock.expect;
4+
import static org.easymock.EasyMock.isA;
5+
6+
import java.io.File;
7+
import java.util.concurrent.ThreadFactory;
8+
9+
import org.jooby.spi.HttpHandler;
10+
import org.jooby.spi.Server;
11+
import org.jooby.test.MockUnit;
12+
import org.jooby.test.MockUnit.Block;
13+
import org.junit.Test;
14+
import org.junit.runner.RunWith;
15+
import org.powermock.core.classloader.annotations.PrepareForTest;
16+
import org.powermock.modules.junit4.PowerMockRunner;
17+
18+
import com.typesafe.config.Config;
19+
import com.typesafe.config.ConfigFactory;
20+
import com.typesafe.config.ConfigValueFactory;
21+
22+
import io.netty.bootstrap.ServerBootstrap;
23+
import io.netty.channel.Channel;
24+
import io.netty.channel.ChannelFuture;
25+
import io.netty.channel.ChannelOption;
26+
import io.netty.channel.EventLoopGroup;
27+
import io.netty.channel.ServerChannel;
28+
import io.netty.channel.epoll.Epoll;
29+
import io.netty.channel.epoll.EpollEventLoopGroup;
30+
import io.netty.channel.nio.NioEventLoopGroup;
31+
import io.netty.channel.socket.nio.NioServerSocketChannel;
32+
import io.netty.handler.logging.LogLevel;
33+
import io.netty.handler.logging.LoggingHandler;
34+
import io.netty.handler.ssl.SslContextBuilder;
35+
import io.netty.util.concurrent.DefaultEventExecutorGroup;
36+
import io.netty.util.concurrent.DefaultThreadFactory;
37+
import io.netty.util.concurrent.EventExecutorGroup;
38+
import io.netty.util.concurrent.Future;
39+
40+
@RunWith(PowerMockRunner.class)
41+
@PrepareForTest({NettyServer.class, NioEventLoopGroup.class, DefaultThreadFactory.class,
42+
DefaultEventExecutorGroup.class, ServerBootstrap.class, SslContextBuilder.class, File.class,
43+
Epoll.class, EpollEventLoopGroup.class, NettySslContext.class })
44+
public class Issue581 {
45+
46+
Config config = ConfigFactory.empty()
47+
.withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(false))
48+
.withValue("netty.threads.Boss", ConfigValueFactory.fromAnyRef(1))
49+
.withValue("netty.threads.Worker", ConfigValueFactory.fromAnyRef(0))
50+
.withValue("netty.threads.Max", ConfigValueFactory.fromAnyRef(2))
51+
.withValue("netty.threads.Name", ConfigValueFactory.fromAnyRef("netty task"))
52+
.withValue("netty.options.SO_BACKLOG", ConfigValueFactory.fromAnyRef(1024))
53+
.withValue("netty.worker.options.SO_REUSEADDR", ConfigValueFactory.fromAnyRef(true))
54+
.withValue("netty.http.MaxContentLength", ConfigValueFactory.fromAnyRef("200k"))
55+
.withValue("netty.http.MaxInitialLineLength", ConfigValueFactory.fromAnyRef("4k"))
56+
.withValue("netty.http.MaxHeaderSize", ConfigValueFactory.fromAnyRef("8k"))
57+
.withValue("netty.http.MaxChunkSize", ConfigValueFactory.fromAnyRef("8k"))
58+
.withValue("netty.http.IdleTimeout", ConfigValueFactory.fromAnyRef("30s"))
59+
.withValue("netty.options.CONNECT_TIMEOUT_MILLIS", ConfigValueFactory.fromAnyRef(1000))
60+
.withValue("application.port", ConfigValueFactory.fromAnyRef(6789))
61+
.withValue("application.host", ConfigValueFactory.fromAnyRef("0.0.0.0"));
62+
63+
@SuppressWarnings({"rawtypes", "unchecked" })
64+
private MockUnit.Block parentEventLoop = unit -> {
65+
NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class)
66+
.args(int.class, ThreadFactory.class)
67+
.build(1, unit.get(ThreadFactory.class));
68+
unit.registerMock(EventLoopGroup.class, eventLoop);
69+
unit.registerMock(NioEventLoopGroup.class, eventLoop);
70+
71+
Future future = unit.mock(Future.class);
72+
expect(eventLoop.shutdownGracefully()).andReturn(future);
73+
expect(eventLoop.isShutdown()).andReturn(true);
74+
};
75+
76+
private Block taskThreadFactory = unit -> {
77+
DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class)
78+
.args(String.class)
79+
.build("netty task");
80+
unit.registerMock(DefaultThreadFactory.class, factory);
81+
};
82+
83+
private Block taskExecutor = unit -> {
84+
DefaultEventExecutorGroup executor = unit.constructor(DefaultEventExecutorGroup.class)
85+
.args(int.class, ThreadFactory.class)
86+
.build(2, unit.get(DefaultThreadFactory.class));
87+
unit.registerMock(EventExecutorGroup.class, executor);
88+
};
89+
90+
private Block channel = unit -> {
91+
ChannelFuture cfuture = unit.mock(ChannelFuture.class);
92+
expect(cfuture.sync()).andReturn(cfuture);
93+
94+
Channel channel = unit.mock(Channel.class);
95+
expect(channel.closeFuture()).andReturn(cfuture);
96+
unit.registerMock(Channel.class, channel);
97+
};
98+
99+
private Block noepoll = unit -> {
100+
unit.mockStatic(Epoll.class);
101+
expect(Epoll.isAvailable()).andReturn(false).times(1, 2);
102+
};
103+
104+
@SuppressWarnings({"rawtypes", "unchecked" })
105+
@Test
106+
public void shouldShutdownExecutor() throws Exception {
107+
new MockUnit(HttpHandler.class)
108+
.expect(parentThreadFactory("nio-boss"))
109+
.expect(noepoll)
110+
.expect(parentEventLoop)
111+
.expect(taskThreadFactory)
112+
.expect(taskExecutor)
113+
.expect(channel)
114+
.expect(bootstrap(6789))
115+
.expect(unit -> {
116+
EventExecutorGroup executor = unit.get(EventExecutorGroup.class);
117+
Future shutdownFuture = unit.mock(Future.class);
118+
expect(executor.shutdownGracefully()).andReturn(shutdownFuture);
119+
})
120+
.run(unit -> {
121+
NettyServer server = new NettyServer(unit.get(HttpHandler.class), config);
122+
try {
123+
server.start();
124+
server.join();
125+
} finally {
126+
server.stop();
127+
}
128+
});
129+
}
130+
131+
private Block parentThreadFactory(final String name) {
132+
return unit -> {
133+
DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class)
134+
.args(String.class, int.class)
135+
.build(name, false);
136+
unit.registerMock(ThreadFactory.class, factory);
137+
};
138+
}
139+
140+
private Block bootstrap(final int port) {
141+
return bootstrap(port, NioEventLoopGroup.class, NioServerSocketChannel.class);
142+
}
143+
144+
private Block bootstrap(final int port, final Class<? extends EventLoopGroup> eventLoop,
145+
final Class<? extends ServerChannel> channelClass) {
146+
return unit -> {
147+
ServerBootstrap bootstrap = unit.mockConstructor(ServerBootstrap.class);
148+
149+
expect(bootstrap.group(
150+
unit.get(EventLoopGroup.class), unit.get(eventLoop)))
151+
.andReturn(bootstrap);
152+
153+
LoggingHandler handler = unit.constructor(LoggingHandler.class)
154+
.args(Class.class, LogLevel.class)
155+
.build(Server.class, LogLevel.DEBUG);
156+
157+
expect(bootstrap.channel(channelClass)).andReturn(bootstrap);
158+
expect(bootstrap.handler(handler)).andReturn(bootstrap);
159+
expect(bootstrap.childHandler(isA(NettyPipeline.class))).andReturn(bootstrap);
160+
161+
expect(bootstrap.option(ChannelOption.SO_BACKLOG, 1024)).andReturn(bootstrap);
162+
expect(bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)).andReturn(bootstrap);
163+
expect(bootstrap.childOption(ChannelOption.SO_REUSEADDR, true)).andReturn(bootstrap);
164+
165+
ChannelFuture future = unit.mock(ChannelFuture.class);
166+
expect(future.sync()).andReturn(future);
167+
expect(future.channel()).andReturn(unit.get(Channel.class));
168+
169+
expect(bootstrap.bind("0.0.0.0", port)).andReturn(future);
170+
171+
unit.registerMock(ServerBootstrap.class, bootstrap);
172+
};
173+
}
174+
175+
}

jooby/src/main/java/org/jooby/Env.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545

4646
import javaslang.API;
4747
import javaslang.control.Option;
48-
import javaslang.control.Try;
4948
import javaslang.control.Try.CheckedConsumer;
5049

5150
/**
@@ -325,7 +324,7 @@ public Env onStart(final CheckedConsumer<Registry> task) {
325324
}
326325

327326
@Override
328-
public LifeCycle onStarted(CheckedConsumer<Registry> task) {
327+
public LifeCycle onStarted(final CheckedConsumer<Registry> task) {
329328
this.started.add(task);
330329
return this;
331330
}

jooby/src/test/java/org/jooby/EnvTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.util.Optional;
1212
import java.util.function.Function;
1313

14-
import javaslang.control.Try;
1514
import org.junit.Test;
1615

1716
import com.google.common.collect.ImmutableMap;
@@ -124,7 +123,7 @@ public LifeCycle onStart(final CheckedConsumer<Registry> task) {
124123
}
125124

126125
@Override
127-
public LifeCycle onStarted(CheckedConsumer<Registry> task) { return null; }
126+
public LifeCycle onStarted(final CheckedConsumer<Registry> task) { return null; }
128127

129128
@Override
130129
public LifeCycle onStop(final CheckedConsumer<Registry> task) {

0 commit comments

Comments
 (0)