Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit 48990cd

Browse files
committed
Failed to submit an exceptionCaught() event fix jooby-project#603
1 parent 5c7bfb0 commit 48990cd

File tree

4 files changed

+144
-25
lines changed

4 files changed

+144
-25
lines changed

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

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import java.lang.reflect.Field;
2626
import java.lang.reflect.ParameterizedType;
27+
import java.util.Iterator;
2728
import java.util.Map;
2829
import java.util.Map.Entry;
2930
import java.util.Optional;
@@ -38,6 +39,7 @@
3839
import org.slf4j.Logger;
3940
import org.slf4j.LoggerFactory;
4041

42+
import com.google.common.collect.ImmutableList;
4143
import com.google.common.collect.Maps;
4244
import com.typesafe.config.Config;
4345

@@ -54,18 +56,12 @@
5456
import io.netty.handler.logging.LogLevel;
5557
import io.netty.handler.logging.LoggingHandler;
5658
import io.netty.handler.ssl.SslContext;
57-
import io.netty.util.ResourceLeakDetector;
5859
import io.netty.util.concurrent.DefaultEventExecutorGroup;
5960
import io.netty.util.concurrent.DefaultThreadFactory;
6061
import io.netty.util.concurrent.EventExecutorGroup;
61-
import javaslang.control.Try;
6262

6363
public class NettyServer implements Server {
6464

65-
static {
66-
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
67-
}
68-
6965
/** The logging system. */
7066
private final Logger log = LoggerFactory.getLogger(Server.class);
7167

@@ -99,8 +95,7 @@ public void start() throws Exception {
9995
}
10096

10197
ThreadFactory threadFactory = new DefaultThreadFactory(conf.getString("netty.threads.Name"));
102-
this.executor = new DefaultEventExecutorGroup(
103-
conf.getInt("netty.threads.Max"), threadFactory);
98+
this.executor = new DefaultEventExecutorGroup(conf.getInt("netty.threads.Max"), threadFactory);
10499

105100
this.ch = bootstrap(executor, null, conf.getInt("application.port"));
106101

@@ -139,14 +134,7 @@ private String host(final String host) {
139134

140135
@Override
141136
public void stop() throws Exception {
142-
Try.run(() -> executor.shutdownGracefully())
143-
.onFailure(x -> log.debug("executor shutdown resulted in exception", x));
144-
Try.run(() -> bossLoop.shutdownGracefully())
145-
.onFailure(x -> log.debug("event loop shutdown resulted in exception", x));
146-
if (!workerLoop.isShutdown()) {
147-
Try.run(() -> workerLoop.shutdownGracefully())
148-
.onFailure(x -> log.debug("worker event loop shutdown resulted in exception", x));
149-
}
137+
shutdownGracefully(ImmutableList.of(bossLoop, workerLoop, executor).iterator());
150138
}
151139

152140
@Override
@@ -203,4 +191,23 @@ private EventLoopGroup eventLoop(final int threads, final String name) {
203191
return new NioEventLoopGroup(threads, new DefaultThreadFactory("nio-" + name, false));
204192
}
205193

194+
/**
195+
* Shutdown executor in order.
196+
*
197+
* @param iterator Executors to shutdown.
198+
*/
199+
private void shutdownGracefully(final Iterator<EventExecutorGroup> iterator) {
200+
if (iterator.hasNext()) {
201+
EventExecutorGroup group = iterator.next();
202+
if (!group.isShuttingDown()) {
203+
group.shutdownGracefully().addListener(future -> {
204+
if (!future.isSuccess()) {
205+
log.debug("shutdown of {} resulted in exception", group, future.cause());
206+
}
207+
shutdownGracefully(iterator);
208+
});
209+
}
210+
}
211+
}
212+
206213
}

jooby-netty/src/test/java/org/jooby/internal/netty/Issue581.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.netty.util.concurrent.DefaultThreadFactory;
3737
import io.netty.util.concurrent.EventExecutorGroup;
3838
import io.netty.util.concurrent.Future;
39+
import io.netty.util.concurrent.GenericFutureListener;
3940

4041
@RunWith(PowerMockRunner.class)
4142
@PrepareForTest({NettyServer.class, NioEventLoopGroup.class, DefaultThreadFactory.class,
@@ -69,8 +70,12 @@ public class Issue581 {
6970
unit.registerMock(NioEventLoopGroup.class, eventLoop);
7071

7172
Future future = unit.mock(Future.class);
72-
expect(eventLoop.shutdownGracefully()).andReturn(future);
73-
expect(eventLoop.isShutdown()).andReturn(true);
73+
unit.registerMock(Future.class, future);
74+
expect(future.isSuccess()).andReturn(true).times(2);
75+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future)
76+
.times(2);
77+
expect(eventLoop.shutdownGracefully()).andReturn(future).times(2);
78+
expect(eventLoop.isShuttingDown()).andReturn(false).times(2);
7479
};
7580

7681
private Block taskThreadFactory = unit -> {
@@ -114,8 +119,10 @@ public void shouldShutdownExecutor() throws Exception {
114119
.expect(bootstrap(6789))
115120
.expect(unit -> {
116121
EventExecutorGroup executor = unit.get(EventExecutorGroup.class);
117-
Future shutdownFuture = unit.mock(Future.class);
118-
expect(executor.shutdownGracefully()).andReturn(shutdownFuture);
122+
Future future = unit.mock(Future.class);
123+
expect(executor.shutdownGracefully()).andReturn(future);
124+
expect(executor.isShuttingDown()).andReturn(false);
125+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
119126
})
120127
.run(unit -> {
121128
NettyServer server = new NettyServer(unit.get(HttpHandler.class), config);
@@ -125,6 +132,11 @@ public void shouldShutdownExecutor() throws Exception {
125132
} finally {
126133
server.stop();
127134
}
135+
}, unit -> {
136+
unit.captured(GenericFutureListener.class).get(0)
137+
.operationComplete(unit.get(Future.class));
138+
unit.captured(GenericFutureListener.class).get(0)
139+
.operationComplete(unit.get(Future.class));
128140
});
129141
}
130142

jooby-netty/src/test/java/org/jooby/internal/netty/NettyServerTest.java

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import static org.easymock.EasyMock.isA;
88

99
import java.io.File;
10+
import java.io.IOException;
11+
import java.util.List;
1012
import java.util.concurrent.ThreadFactory;
1113

1214
import org.jooby.spi.HttpHandler;
@@ -42,11 +44,12 @@
4244
import io.netty.util.concurrent.DefaultThreadFactory;
4345
import io.netty.util.concurrent.EventExecutorGroup;
4446
import io.netty.util.concurrent.Future;
47+
import io.netty.util.concurrent.GenericFutureListener;
4548

4649
@RunWith(PowerMockRunner.class)
4750
@PrepareForTest({NettyServer.class, NioEventLoopGroup.class, DefaultThreadFactory.class,
4851
DefaultEventExecutorGroup.class, ServerBootstrap.class, SslContextBuilder.class, File.class,
49-
Epoll.class, EpollEventLoopGroup.class, NettySslContext.class })
52+
Epoll.class, EpollEventLoopGroup.class, NettySslContext.class, GenericFutureListener.class })
5053
public class NettyServerTest {
5154

5255
Config config = ConfigFactory.empty()
@@ -75,8 +78,9 @@ public class NettyServerTest {
7578
unit.registerMock(NioEventLoopGroup.class, eventLoop);
7679

7780
Future future = unit.mock(Future.class);
81+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
7882
expect(eventLoop.shutdownGracefully()).andReturn(future);
79-
expect(eventLoop.isShutdown()).andReturn(true);
83+
expect(eventLoop.isShuttingDown()).andReturn(false);
8084
};
8185

8286
private Block taskThreadFactory = unit -> {
@@ -130,6 +134,35 @@ public void defaultServer() throws Exception {
130134
});
131135
}
132136

137+
@Test
138+
public void eventLoopIsShuttingDown() throws Exception {
139+
new MockUnit(HttpHandler.class)
140+
.expect(parentThreadFactory("nio-boss"))
141+
.expect(noepoll)
142+
.expect(unit -> {
143+
NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class)
144+
.args(int.class, ThreadFactory.class)
145+
.build(1, unit.get(ThreadFactory.class));
146+
unit.registerMock(EventLoopGroup.class, eventLoop);
147+
unit.registerMock(NioEventLoopGroup.class, eventLoop);
148+
149+
expect(eventLoop.isShuttingDown()).andReturn(true);
150+
})
151+
.expect(taskThreadFactory)
152+
.expect(taskExecutor)
153+
.expect(channel)
154+
.expect(bootstrap(6789))
155+
.run(unit -> {
156+
NettyServer server = new NettyServer(unit.get(HttpHandler.class), config);
157+
try {
158+
server.start();
159+
server.join();
160+
} finally {
161+
server.stop();
162+
}
163+
});
164+
}
165+
133166
private Block parentThreadFactory(final String name) {
134167
return unit -> {
135168
DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class)
@@ -156,8 +189,9 @@ public void epollServer() throws Exception {
156189
unit.registerMock(EpollEventLoopGroup.class, eventLoop);
157190

158191
Future future = unit.mock(Future.class);
192+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
159193
expect(eventLoop.shutdownGracefully()).andReturn(future);
160-
expect(eventLoop.isShutdown()).andReturn(true);
194+
expect(eventLoop.isShuttingDown()).andReturn(false);
161195
})
162196
.expect(taskThreadFactory)
163197
.expect(taskExecutor)
@@ -216,7 +250,65 @@ public void serverWithWorkerEventLoop() throws Exception {
216250
unit.registerMock(EventLoopGroup.class, eventLoop);
217251

218252
Future future = unit.mock(Future.class);
253+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
254+
expect(eventLoop.shutdownGracefully()).andReturn(future);
255+
expect(eventLoop.isShuttingDown()).andReturn(false);
256+
})
257+
.expect(unit -> {
258+
DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class)
259+
.args(String.class, int.class)
260+
.build("nio-worker", false);
261+
unit.registerMock(DefaultThreadFactory.class, factory);
262+
})
263+
.expect(unit -> {
264+
NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class)
265+
.args(int.class, ThreadFactory.class)
266+
.build(1, unit.get(DefaultThreadFactory.class));
267+
unit.registerMock(NioEventLoopGroup.class, eventLoop);
268+
269+
Future future = unit.mock(Future.class);
270+
expect(future.isSuccess()).andReturn(true);
271+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
272+
expect(eventLoop.shutdownGracefully()).andReturn(future);
273+
expect(eventLoop.isShuttingDown()).andReturn(false);
274+
unit.registerMock(Future.class, future);
275+
})
276+
.expect(taskThreadFactory)
277+
.expect(taskExecutor)
278+
.expect(channel)
279+
.expect(bootstrap(6789))
280+
.run(unit -> {
281+
NettyServer server = new NettyServer(unit.get(HttpHandler.class), config);
282+
try {
283+
server.start();
284+
server.join();
285+
} finally {
286+
server.stop();
287+
}
288+
}, unit -> {
289+
List<GenericFutureListener> captured = unit.captured(GenericFutureListener.class);
290+
captured.get(0)
291+
.operationComplete(unit.get(Future.class));
292+
});
293+
}
294+
295+
@SuppressWarnings({"unchecked", "rawtypes" })
296+
@Test
297+
public void eventLoopError() throws Exception {
298+
Config config = this.config.withValue("netty.threads.Worker", ConfigValueFactory.fromAnyRef(1));
299+
new MockUnit(HttpHandler.class)
300+
.expect(parentThreadFactory("nio-boss"))
301+
.expect(noepoll)
302+
.expect(unit -> {
303+
NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class)
304+
.args(int.class, ThreadFactory.class)
305+
.build(1, unit.get(ThreadFactory.class));
306+
unit.registerMock(EventLoopGroup.class, eventLoop);
307+
308+
Future future = unit.mock(Future.class);
309+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
219310
expect(eventLoop.shutdownGracefully()).andReturn(future);
311+
expect(eventLoop.isShuttingDown()).andReturn(false);
220312
})
221313
.expect(unit -> {
222314
DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class)
@@ -231,8 +323,12 @@ public void serverWithWorkerEventLoop() throws Exception {
231323
unit.registerMock(NioEventLoopGroup.class, eventLoop);
232324

233325
Future future = unit.mock(Future.class);
234-
expect(eventLoop.isShutdown()).andReturn(false);
326+
expect(future.isSuccess()).andReturn(false);
327+
expect(future.cause()).andReturn(new IOException("intentional err"));
328+
expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future);
235329
expect(eventLoop.shutdownGracefully()).andReturn(future);
330+
expect(eventLoop.isShuttingDown()).andReturn(false);
331+
unit.registerMock(Future.class, future);
236332
})
237333
.expect(taskThreadFactory)
238334
.expect(taskExecutor)
@@ -246,6 +342,10 @@ public void serverWithWorkerEventLoop() throws Exception {
246342
} finally {
247343
server.stop();
248344
}
345+
}, unit -> {
346+
List<GenericFutureListener> captured = unit.captured(GenericFutureListener.class);
347+
captured.get(0)
348+
.operationComplete(unit.get(Future.class));
249349
});
250350
}
251351

jooby/src/test/java/org/jooby/test/MockUnit.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public <T> T capture(final Class<T> type) {
111111
public <T> List<T> captured(final Class<T> type) {
112112
List<Capture<Object>> captureList = this.captures.get(type);
113113
List<T> result = new LinkedList<>();
114-
captureList.forEach(it -> result.add((T) it.getValue()));
114+
captureList.stream().filter(Capture::hasCaptured).forEach(it -> result.add((T) it.getValue()));
115115
return result;
116116
}
117117

0 commit comments

Comments
 (0)