Skip to content

Commit 7f44778

Browse files
committed
jooby-netty: possible leak in scheduled tasks under HTTP traffic
- only affect 4.x - fix #3871
1 parent e84e2f9 commit 7f44778

File tree

3 files changed

+42
-13
lines changed

3 files changed

+42
-13
lines changed

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyDateService.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,25 @@
88
import java.time.ZoneOffset;
99
import java.time.ZonedDateTime;
1010
import java.time.format.DateTimeFormatter;
11-
import java.util.concurrent.ScheduledExecutorService;
12-
import java.util.concurrent.TimeUnit;
11+
import java.util.concurrent.*;
12+
13+
import io.netty.util.concurrent.DefaultThreadFactory;
1314

1415
public class NettyDateService implements Runnable {
1516
private static final int DATE_INTERVAL = 1000;
16-
private CharSequence date;
17+
private final ScheduledExecutorService scheduler;
18+
// Make volatile for thread visibility across EventLoop threads
19+
private volatile CharSequence date;
20+
private final ScheduledFuture<?> future;
1721

18-
public NettyDateService(ScheduledExecutorService scheduler) {
22+
public NettyDateService() {
23+
this.scheduler =
24+
Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("http-date-keeper"));
1925
this.date =
2026
new NettyString(
2127
DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
22-
scheduler.scheduleAtFixedRate(this, DATE_INTERVAL, DATE_INTERVAL, TimeUnit.MILLISECONDS);
28+
this.future =
29+
scheduler.scheduleAtFixedRate(this, DATE_INTERVAL, DATE_INTERVAL, TimeUnit.MILLISECONDS);
2330
}
2431

2532
public CharSequence date() {
@@ -32,4 +39,17 @@ public void run() {
3239
new NettyString(
3340
DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
3441
}
42+
43+
// Allow the server to gracefully terminate the background task
44+
public void stop() {
45+
try {
46+
if (this.future != null) {
47+
this.future.cancel(false);
48+
}
49+
} catch (Exception ignored) {
50+
if (scheduler != null) {
51+
scheduler.shutdown();
52+
}
53+
}
54+
}
3555
}

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package io.jooby.internal.netty;
77

88
import java.util.List;
9-
import java.util.concurrent.ScheduledExecutorService;
109

1110
import io.jooby.Context;
1211
import io.netty.buffer.ByteBuf;
@@ -32,6 +31,7 @@ public class NettyPipeline extends ChannelInitializer<SocketChannel> {
3231
private final boolean http2;
3332
private final boolean expectContinue;
3433
private final Integer compressionLevel;
34+
private final NettyDateService dateService;
3535

3636
public NettyPipeline(
3737
SslContext sslContext,
@@ -43,7 +43,8 @@ public NettyPipeline(
4343
boolean defaultHeaders,
4444
boolean http2,
4545
boolean expectContinue,
46-
Integer compressionLevel) {
46+
Integer compressionLevel,
47+
NettyDateService dateService) {
4748
this.sslContext = sslContext;
4849
this.decoderConfig = decoderConfig;
4950
this.contextSelector = contextSelector;
@@ -54,6 +55,7 @@ public NettyPipeline(
5455
this.http2 = http2;
5556
this.expectContinue = expectContinue;
5657
this.compressionLevel = compressionLevel;
58+
this.dateService = dateService;
5759
}
5860

5961
@Override
@@ -74,7 +76,7 @@ public void initChannel(SocketChannel ch) {
7476
private void setupHttp11(ChannelPipeline p) {
7577
p.addLast("codec", createServerCodec());
7678
addCommonHandlers(p);
77-
p.addLast("handler", createHandler(p.channel().eventLoop()));
79+
p.addLast("handler", createHandler());
7880
}
7981

8082
private void setupHttp2(ChannelPipeline pipeline) {
@@ -100,7 +102,7 @@ private void setupHttp11Upgrade(ChannelPipeline pipeline) {
100102
(int) maxRequestSize));
101103

102104
addCommonHandlers(pipeline);
103-
pipeline.addLast("handler", createHandler(pipeline.channel().eventLoop()));
105+
pipeline.addLast("handler", createHandler());
104106
}
105107

106108
private ChannelInboundHandler setupHttp2Handshake(boolean secure) {
@@ -126,9 +128,9 @@ private Http2ServerUpgradeCodec createH2CUpgradeCodec() {
126128
new Http2MultiplexHandler(new Http2StreamInitializer(this)));
127129
}
128130

129-
private NettyHandler createHandler(ScheduledExecutorService executor) {
131+
private NettyHandler createHandler() {
130132
return new NettyHandler(
131-
new NettyDateService(executor),
133+
dateService,
132134
contextSelector,
133135
maxRequestSize,
134136
maxFormFields,
@@ -193,7 +195,7 @@ private static class Http2StreamInitializer extends ChannelInitializer<Channel>
193195
@Override
194196
protected void initChannel(Channel ch) {
195197
ch.pipeline().addLast("http2", new Http2StreamFrameToHttpObjectCodec(true));
196-
ch.pipeline().addLast("handler", pipeline.createHandler(ch.eventLoop()));
198+
ch.pipeline().addLast("handler", pipeline.createHandler());
197199
}
198200
}
199201
}

modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class NettyServer extends Server.Base {
6161
private NettyOutputFactory outputFactory;
6262
private boolean singleEventLoopGroup =
6363
System.getProperty("io.netty.eventLoopGroup", "parent-child").equals("single");
64+
private NettyDateService dateLoop;
6465

6566
/**
6667
* Creates a server.
@@ -147,6 +148,7 @@ public Server start(@NonNull Jooby... application) {
147148
new NettyEventLoopGroupImpl(
148149
transport, singleEventLoopGroup, options.getIoThreads(), worker);
149150
}
151+
this.dateLoop = new NettyDateService();
150152

151153
fireStart(List.of(application), eventLoop.worker());
152154

@@ -232,7 +234,8 @@ private NettyPipeline newPipeline(ServerOptions options, SslContext sslContext,
232234
options.getDefaultHeaders(),
233235
http2,
234236
options.isExpectContinue() == Boolean.TRUE,
235-
options.getCompressionLevel());
237+
options.getCompressionLevel(),
238+
dateLoop);
236239
}
237240

238241
@Override
@@ -241,6 +244,10 @@ public synchronized Server stop() {
241244
// only for jooby build where close events may take longer.
242245
NettyWebSocket.all.clear();
243246

247+
if (this.dateLoop != null) {
248+
this.dateLoop.stop();
249+
}
250+
244251
if (eventLoop != null) {
245252
eventLoop.shutdown();
246253
}

0 commit comments

Comments
 (0)