Skip to content

Commit 24df466

Browse files
committed
Apply some micro-optimization discovered by JProfiler #775
1 parent 3714c9e commit 24df466

35 files changed

+788
-477
lines changed

coverage-report/pom.xml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.jooby</groupId>
77
<artifactId>jooby-project</artifactId>
8-
<version>1.1.1</version>
8+
<version>1.1.2-SNAPSHOT</version>
99
</parent>
1010

1111
<modelVersion>4.0.0</modelVersion>
@@ -229,6 +229,7 @@
229229
<groupId>org.apache.maven.plugins</groupId>
230230
<artifactId>maven-surefire-plugin</artifactId>
231231
<configuration>
232+
<skip>false</skip>
232233
<includes>
233234
<include>**/*Test.java</include>
234235
<include>**/*Feature.java</include>
@@ -288,11 +289,7 @@
288289
<groupId>org.apache.maven.plugins</groupId>
289290
<artifactId>maven-surefire-plugin</artifactId>
290291
<configuration>
291-
<includes>
292-
<include>**/*Test.java</include>
293-
<include>**/*Feature.java</include>
294-
<include>**/*Issue*.java</include>
295-
</includes>
292+
<skip>true</skip>
296293
</configuration>
297294
</plugin>
298295

coverage-report/src/test/java/org/jooby/ContentNegotiationFeature.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,18 @@ public String json() {
4242

4343
renderer(BodyConverters.toJson);
4444

45-
get("/any", req ->
46-
Results
47-
.when("text/html", () -> Results.html("test").put("this", "body"))
48-
.when("*/*", () -> "body"));
45+
get("/any", req -> Results
46+
.when("text/html", () -> Results.html("test").put("this", "body"))
47+
.when("*/*", () -> "body"));
4948

50-
get("/status", req ->
51-
Results
52-
.when("*", () -> Status.NOT_ACCEPTABLE));
49+
get("/status", req -> Results
50+
.when("*", () -> {
51+
throw new Err(Status.NOT_ACCEPTABLE);
52+
}));
5353

54-
get("/like", req ->
55-
Results
56-
.when("text/html", () -> Results.html("test").put("this", "body"))
57-
.when("application/json", () -> "body"));
54+
get("/like", req -> Results
55+
.when("text/html", () -> Results.html("test").put("this", "body"))
56+
.when("application/json", () -> "body"));
5857

5958
get("/html", (req, resp) -> resp.send(Results.html("test").put("this", "body")))
6059
.produces(MediaType.html);
@@ -205,10 +204,10 @@ public void fallback() throws Exception {
205204

206205
@Test
207206
public void like() throws Exception {
208-
// request()
209-
// .get("/like")
210-
// .header("Accept", "application/*+json")
211-
// .expect("{\"body\": \"body\"}");
207+
// request()
208+
// .get("/like")
209+
// .header("Accept", "application/*+json")
210+
// .expect("{\"body\": \"body\"}");
212211

213212
request()
214213
.get("/like")

coverage-report/src/test/java/org/jooby/RouteReferenceFeature.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ public class RouteReferenceFeature extends ServerFeature {
1717
assertEquals("GET", route.method());
1818
assertEquals("/", route.path());
1919
assertEquals("/", route.pattern());
20+
assertEquals("bar", route.attr("foo"));
2021
assertEquals(
21-
"| Method | Path | Source | Name | Pattern | Consumes | Produces |\n" +
22-
"|--------|------|------------------------------------|------------|---------|----------|----------|\n" +
23-
"| GET | / | org.jooby.RouteReferenceFeature:13 | /anonymous | / | [*/*] | [*/*] |" +
24-
"",
22+
"| Method | Path | Source | Name | Pattern | Consumes | Produces |\n"
23+
+
24+
"|--------|------|------------------------------------|------------|---------|----------|----------|\n"
25+
+
26+
"| GET | / | org.jooby.RouteReferenceFeature:13 | /anonymous | / | [*/*] | [*/*] |"
27+
+
28+
"",
2529
req.toString());
2630
rsp.send("done");
27-
});
31+
}).attr("foo", "bar");
2832

2933
get("/:var", (req, rsp) -> {
3034
Route route = req.route();

coverage.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/bin/bash
22
ALPN_VERSION="`groovy -e 'String v = (System.getProperty("java.vendor").contains("IBM") ? {def m = (["java", "-version"].execute().err.text =~ /(?s)java version "([0-9]*.[0-9]*.[0-9]*)".*Oracle jdk[0-9]u([0-9]*)-.*/); "${m[0][1]}_${m[0][2]}"}.call() : System.getProperty("java.version")); ((new URL("http://www.eclipse.org/jetty/documentation/9.4.x/alpn-chapter.html")).text =~ /ALPN vs. OpenJDK versions(.*)<\/table>/).each { m, s -> ( s =~ /([0-9].[0-9].[0-9]*u[0-9]*)[^0-9]*([0-9].[0-9]*.[0-9]*.v2[0-9]*)/).each { _, jdkv, alpnv -> if (v.equals(jdkv.replace("u","_"))){ println alpnv }}}'`"
33
echo "ALPN $ALPN_VERSION"
4-
cd coverage-report
54
mvn -Dlogback.configurationFile=logback-travis.xml -Dalpn-boot-version=$ALPN_VERSION -DdryRun=true clean package coveralls:report -P coverage

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@
1919
package org.jooby.internal.netty;
2020

2121
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
22-
import static java.util.Objects.requireNonNull;
2322

2423
import org.jooby.internal.ConnectionResetByPeer;
2524
import org.jooby.spi.HttpHandler;
2625
import org.slf4j.Logger;
2726
import org.slf4j.LoggerFactory;
2827

29-
import com.typesafe.config.Config;
30-
3128
import io.netty.channel.ChannelHandlerContext;
3229
import io.netty.channel.SimpleChannelInboundHandler;
3330
import io.netty.handler.codec.http.DefaultFullHttpResponse;
@@ -61,14 +58,12 @@ public class NettyHandler extends SimpleChannelInboundHandler<Object> {
6158

6259
private int bufferSize;
6360

64-
public NettyHandler(final HttpHandler handler, final Config config) {
65-
this.handler = requireNonNull(handler, "Application handler is required.");
66-
this.tmpdir = config.getString("application.tmpdir");
67-
this.bufferSize = config.getBytes("server.http.ResponseBufferSize").intValue();
68-
this.wsMaxMessageSize = Math
69-
.max(
70-
config.getBytes("server.ws.MaxTextMessageSize").intValue(),
71-
config.getBytes("server.ws.MaxBinaryMessageSize").intValue());
61+
public NettyHandler(final HttpHandler handler, final String tmpdir, final int bufferSize,
62+
final int wsBufferSize) {
63+
this.handler = handler;
64+
this.tmpdir = tmpdir;
65+
this.bufferSize = bufferSize;
66+
this.wsMaxMessageSize = wsBufferSize;
7267
}
7368

7469
@Override

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ public class NettyPipeline extends ChannelInitializer<SocketChannel> {
7474

7575
private boolean supportH2;
7676

77+
private String tmpdir;
78+
79+
private int bufferSize;
80+
81+
private int wsMaxMessageSize;
82+
7783
public NettyPipeline(final EventExecutorGroup executor, final HttpHandler handler,
7884
final Config conf, final SslContext sslCtx) {
7985
this.executor = executor;
@@ -86,6 +92,12 @@ public NettyPipeline(final EventExecutorGroup executor, final HttpHandler handle
8692
maxContentLength = conf.getBytes("netty.http.MaxContentLength").intValue();
8793
idleTimeOut = conf.getDuration("netty.http.IdleTimeout", TimeUnit.MILLISECONDS);
8894
supportH2 = conf.getBoolean("server.http2.enabled");
95+
this.tmpdir = config.getString("application.tmpdir");
96+
this.bufferSize = config.getBytes("server.http.ResponseBufferSize").intValue();
97+
this.wsMaxMessageSize = Math
98+
.max(
99+
config.getBytes("server.ws.MaxTextMessageSize").intValue(),
100+
config.getBytes("server.ws.MaxBinaryMessageSize").intValue());
89101
this.sslCtx = sslCtx;
90102
}
91103

@@ -143,7 +155,7 @@ private void aggregator(final ChannelPipeline p) {
143155
}
144156

145157
private void jooby(final ChannelPipeline p) {
146-
p.addLast(executor, "jooby", new NettyHandler(handler, config));
158+
p.addLast(executor, "jooby", new NettyHandler(handler, tmpdir, bufferSize, wsMaxMessageSize));
147159
}
148160

149161
private Http2ConnectionHandler newHttp2ConnectionHandler(final ChannelPipeline p) {

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

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import io.netty.channel.ChannelFuture;
3838
import io.netty.channel.ChannelHandlerContext;
3939
import io.netty.channel.ChannelPipeline;
40+
import io.netty.channel.ChannelPromise;
4041
import io.netty.channel.DefaultFileRegion;
4142
import io.netty.handler.codec.http.DefaultFullHttpResponse;
4243
import io.netty.handler.codec.http.DefaultHttpHeaders;
@@ -131,12 +132,20 @@ public void send(final InputStream stream) throws Exception {
131132
} else {
132133
DefaultHttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
133134

134-
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
135+
boolean lenSet = headers.contains(HttpHeaderNames.CONTENT_LENGTH);
136+
final boolean keepAlive;
137+
final ChannelPromise promise;
138+
if (!lenSet) {
135139
headers.set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
140+
keepAlive = false;
141+
promise = ctx.newPromise();
142+
} else if (this.keepAlive) {
143+
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
144+
keepAlive = this.keepAlive;
145+
promise = ctx.voidPromise();
136146
} else {
137-
if (keepAlive) {
138-
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
139-
}
147+
keepAlive = false;
148+
promise = ctx.newPromise();
140149
}
141150

142151
// dump headers
@@ -155,7 +164,10 @@ public void send(final InputStream stream) throws Exception {
155164
ctx.write(buffer);
156165
// send tail
157166
ctx.write(new ChunkedStream(stream, bufferSize));
158-
keepAlive(ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT));
167+
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, promise);
168+
if (!keepAlive) {
169+
future.addListener(CLOSE);
170+
}
159171
});
160172
}
161173

@@ -171,10 +183,8 @@ public void send(final FileChannel channel) throws Exception {
171183
public void send(final FileChannel channel, final long offset, final long count)
172184
throws Exception {
173185
DefaultHttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
174-
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
175-
headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
176-
headers.set(HttpHeaderNames.CONTENT_LENGTH, count);
177-
}
186+
headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
187+
headers.set(HttpHeaderNames.CONTENT_LENGTH, count);
178188

179189
if (keepAlive) {
180190
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
@@ -198,17 +208,25 @@ public void send(final FileChannel channel, final long offset, final long count)
198208

199209
ctx.channel().eventLoop().execute(() -> {
200210
// send headers
201-
ctx.write(rsp);
211+
ctx.write(rsp, ctx.voidPromise());
202212
// chunked file
203-
keepAlive(ctx.writeAndFlush(chunkedInput));
213+
if (keepAlive) {
214+
ctx.writeAndFlush(chunkedInput, ctx.voidPromise());
215+
} else {
216+
ctx.writeAndFlush(chunkedInput).addListener(CLOSE);
217+
}
204218
});
205219
} else {
206220
ctx.channel().eventLoop().execute(() -> {
207221
// send headers
208-
ctx.write(rsp);
222+
ctx.write(rsp, ctx.voidPromise());
209223
// file region
210-
ctx.write(new DefaultFileRegion(channel, offset, count));
211-
keepAlive(ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT));
224+
ctx.write(new DefaultFileRegion(channel, offset, count), ctx.voidPromise());
225+
if (keepAlive) {
226+
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, ctx.voidPromise());
227+
} else {
228+
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(CLOSE);
229+
}
212230
});
213231
}
214232

@@ -219,39 +237,33 @@ public void send(final FileChannel channel, final long offset, final long count)
219237
private void send(final ByteBuf buffer) throws Exception {
220238
DefaultFullHttpResponse rsp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buffer);
221239

222-
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
223-
headers.remove(HttpHeaderNames.TRANSFER_ENCODING)
224-
.set(HttpHeaderNames.CONTENT_LENGTH, buffer.readableBytes());
225-
}
240+
headers.remove(HttpHeaderNames.TRANSFER_ENCODING)
241+
.set(HttpHeaderNames.CONTENT_LENGTH, buffer.readableBytes());
226242

243+
ChannelPromise promise;
227244
if (keepAlive) {
245+
promise = ctx.voidPromise();
228246
headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
247+
} else {
248+
promise = ctx.newPromise();
229249
}
230250

231251
// dump headers
232252
rsp.headers().set(headers);
233253

234254
Attribute<Boolean> async = ctx.channel().attr(NettyRequest.ASYNC);
235-
boolean isAsync = async != null && async.get() == Boolean.TRUE;
236-
if (isAsync) {
237-
// we need flush, from async
238-
keepAlive(ctx.writeAndFlush(rsp));
255+
boolean flush = async != null && async.get() == Boolean.TRUE;
256+
final ChannelFuture future;
257+
if (flush) {
258+
future = ctx.writeAndFlush(rsp, promise);
239259
} else {
240-
keepAlive(ctx.write(rsp));
260+
future = ctx.write(rsp, promise);
241261
}
242-
243-
committed = true;
244-
}
245-
246-
private void keepAlive(final ChannelFuture future) {
247-
if (headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
248-
if (!keepAlive) {
249-
future.addListener(CLOSE);
250-
}
251-
} else {
252-
// content len is not set, just close the connection regardless keep alive or not.
262+
if (!keepAlive) {
253263
future.addListener(CLOSE);
254264
}
265+
266+
committed = true;
255267
}
256268

257269
@Override
@@ -282,11 +294,16 @@ public void end() {
282294
}
283295
if (!committed) {
284296
DefaultHttpResponse rsp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
297+
headers.set(HttpHeaderNames.CONTENT_LENGTH, 0);
285298
// dump headers
286299
rsp.headers().set(headers);
287-
keepAlive(ctx.write(rsp));
300+
if (keepAlive) {
301+
ctx.write(rsp, ctx.voidPromise());
302+
} else {
303+
ctx.write(rsp).addListener(CLOSE);
304+
}
305+
committed = true;
288306
}
289-
committed = true;
290307
ctx = null;
291308
}
292309
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import io.netty.handler.logging.LogLevel;
5757
import io.netty.handler.logging.LoggingHandler;
5858
import io.netty.handler.ssl.SslContext;
59+
import io.netty.util.ResourceLeakDetector;
60+
import io.netty.util.ResourceLeakDetector.Level;
5961
import io.netty.util.concurrent.DefaultEventExecutorGroup;
6062
import io.netty.util.concurrent.DefaultThreadFactory;
6163
import io.netty.util.concurrent.EventExecutorGroup;
@@ -81,6 +83,7 @@ public class NettyServer implements Server {
8183
public NettyServer(final HttpHandler dispatcher, final Config config) {
8284
this.dispatcher = dispatcher;
8385
this.conf = config;
86+
ResourceLeakDetector.setLevel(Level.DISABLED);
8487
}
8588

8689
@Override
@@ -143,8 +146,7 @@ public void join() throws InterruptedException {
143146
}
144147

145148
@Override
146-
public Optional<Executor> executor()
147-
{
149+
public Optional<Executor> executor() {
148150
return Optional.ofNullable(executor);
149151
}
150152

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ public class Issue581 {
5959
.withValue("netty.http.IdleTimeout", ConfigValueFactory.fromAnyRef("30s"))
6060
.withValue("netty.options.CONNECT_TIMEOUT_MILLIS", ConfigValueFactory.fromAnyRef(1000))
6161
.withValue("application.port", ConfigValueFactory.fromAnyRef(6789))
62-
.withValue("application.host", ConfigValueFactory.fromAnyRef("0.0.0.0"));
62+
.withValue("application.host", ConfigValueFactory.fromAnyRef("0.0.0.0"))
63+
.withValue("server.http.ResponseBufferSize", ConfigValueFactory.fromAnyRef("16k"))
64+
.withValue("server.ws.MaxTextMessageSize", ConfigValueFactory.fromAnyRef("16k"))
65+
.withValue("server.ws.MaxBinaryMessageSize", ConfigValueFactory.fromAnyRef("16k"))
66+
.withValue("application.tmpdir", ConfigValueFactory.fromAnyRef("target"));
6367

6468
@SuppressWarnings({"rawtypes", "unchecked" })
6569
private MockUnit.Block parentEventLoop = unit -> {

0 commit comments

Comments
 (0)