Skip to content

Commit 542e3ff

Browse files
committed
Using wrapped executor for Jersey async.
Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
1 parent f05f9ff commit 542e3ff

9 files changed

Lines changed: 294 additions & 18 deletions

File tree

dependencies/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
<version.lib.jsonb-api>1.0.2</version.lib.jsonb-api>
7878
<version.lib.jsonp-api>1.1.6</version.lib.jsonp-api>
7979
<version.lib.jsonp-impl>1.1.6</version.lib.jsonp-impl>
80-
<version.lib.junit>5.1.0</version.lib.junit>
80+
<version.lib.junit>5.6.2</version.lib.junit>
8181
<version.lib.maven-wagon>2.10</version.lib.maven-wagon>
8282
<version.lib.microprofile-config>1.3</version.lib.microprofile-config>
8383
<version.lib.microprofile-health>2.1</version.lib.microprofile-health>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright (c) 2020 Oracle and/or its affiliates.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<project xmlns="http://maven.apache.org/POM/4.0.0"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<parent>
23+
<artifactId>helidon-tests-integration</artifactId>
24+
<groupId>io.helidon.tests.integration</groupId>
25+
<version>2.0.0-SNAPSHOT</version>
26+
</parent>
27+
<modelVersion>4.0.0</modelVersion>
28+
29+
<artifactId>helidon-tests-integration-mp-gh-1538</artifactId>
30+
<name>Helidon Tests Integration MP GH 1538</name>
31+
<description>Reproducer for Github issue #1538 - control Jersey
32+
Async executor size</description>
33+
34+
<dependencies>
35+
<dependency>
36+
<groupId>io.helidon.microprofile.bundles</groupId>
37+
<artifactId>helidon-microprofile</artifactId>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.junit.jupiter</groupId>
41+
<artifactId>junit-jupiter-api</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.hamcrest</groupId>
46+
<artifactId>hamcrest-all</artifactId>
47+
<scope>test</scope>
48+
</dependency>
49+
</dependencies>
50+
</project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.tests.integration.gh1538;
18+
19+
import java.util.Set;
20+
21+
import javax.enterprise.context.ApplicationScoped;
22+
import javax.ws.rs.core.Application;
23+
24+
@ApplicationScoped
25+
public class JaxRsApplication extends Application {
26+
@Override
27+
public Set<Class<?>> getClasses() {
28+
return Set.of(JaxRsResource.class);
29+
}
30+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.tests.integration.gh1538;
18+
19+
import javax.enterprise.context.RequestScoped;
20+
import javax.ws.rs.GET;
21+
import javax.ws.rs.Path;
22+
import javax.ws.rs.container.AsyncResponse;
23+
import javax.ws.rs.container.Suspended;
24+
25+
@RequestScoped
26+
@Path("/test")
27+
public class JaxRsResource {
28+
@GET
29+
@Path("/async")
30+
public void asyncResponse(@Suspended AsyncResponse response) {
31+
Thread thread = new Thread(() -> response.resume("result"));
32+
thread.start();
33+
}
34+
35+
@GET
36+
@Path("/sync")
37+
public String syncResponse() {
38+
return Thread.currentThread().getName();
39+
}
40+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#
2+
# Copyright (c) 2020 Oracle and/or its affiliates.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
server.port=0
18+
server.executor-service.thread-name-prefix=gh-1538-
19+
server.jersey.async-executor-service.thread-name-prefix=async-gh-1538-
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.tests.integration.gh1538;
18+
19+
import javax.ws.rs.client.Client;
20+
import javax.ws.rs.client.ClientBuilder;
21+
import javax.ws.rs.client.WebTarget;
22+
23+
import io.helidon.microprofile.server.Server;
24+
25+
import org.junit.jupiter.api.AfterAll;
26+
import org.junit.jupiter.api.BeforeAll;
27+
import org.junit.jupiter.api.MethodOrderer;
28+
import org.junit.jupiter.api.Order;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.TestMethodOrder;
31+
32+
import static org.hamcrest.CoreMatchers.is;
33+
import static org.hamcrest.MatcherAssert.assertThat;
34+
import static org.hamcrest.Matchers.greaterThan;
35+
36+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
37+
class JaxRsResourceTest {
38+
private static Server server;
39+
private static Client client;
40+
private static WebTarget target;
41+
42+
@BeforeAll
43+
static void initClass() {
44+
server = Server.create(JaxRsApplication.class).start();
45+
client = ClientBuilder.newClient();
46+
target = client.target("http://localhost:" + server.port() + "/test");
47+
}
48+
49+
@AfterAll
50+
static void destroyClass() {
51+
if (null != server) {
52+
server.stop();
53+
}
54+
if (null != client) {
55+
client.close();
56+
}
57+
}
58+
59+
@Test
60+
@Order(1)
61+
void testSync() {
62+
target.path("/sync")
63+
.request()
64+
.get(String.class);
65+
}
66+
67+
@Test
68+
@Order(2)
69+
void testAsync() {
70+
target.path("/async")
71+
.request()
72+
.get(String.class);
73+
}
74+
75+
@Test
76+
// this method must be after the async test, as the threads are not created before it
77+
@Order(3)
78+
void testThreads() {
79+
int countOfJerseyServer = 0;
80+
int countOfJerseyServerAsync = 0;
81+
int countOfDefaultJersey = 0;
82+
83+
// now make sure the threads are as expected - array quite large, to fit all threads
84+
Thread[] threads = new Thread[100];
85+
Thread.enumerate(threads);
86+
for (Thread thread : threads) {
87+
if (null == thread) {
88+
break;
89+
}
90+
String threadName = thread.getName();
91+
// see microprofile-config.properties - this is an explicit prefix
92+
if (threadName.startsWith("gh-1538-")) {
93+
countOfJerseyServer++;
94+
} else if (threadName.startsWith("async-gh-1538-")) {
95+
countOfJerseyServerAsync++;
96+
} else if (threadName.startsWith("jersey-server-managed-async-executor-")) {
97+
countOfDefaultJersey++;
98+
} else {
99+
System.out.println(threadName);
100+
}
101+
}
102+
103+
assertThat("We should replace default async executor with a custom one", countOfDefaultJersey, is(0));
104+
assertThat("We should use our configured server threads", countOfJerseyServer, greaterThan(0));
105+
assertThat("We should use our configured server async threads", countOfJerseyServerAsync, greaterThan(0));
106+
}
107+
}

tests/integration/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<module>mp-ws-services</module>
4141
<module>webclient</module>
4242
<module>security</module>
43+
<module>mp-gh-1538</module>
4344
</modules>
4445

4546
<profiles>

webserver/jersey/src/main/java/io/helidon/webserver/jersey/AsyncExecutorProvider.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.helidon.webserver.jersey;
1818

1919
import java.util.concurrent.ExecutorService;
20+
import java.util.function.Supplier;
2021

2122
import io.helidon.common.configurable.ThreadPoolSupplier;
2223
import io.helidon.config.Config;
@@ -26,16 +27,24 @@
2627

2728
@ManagedAsyncExecutor
2829
class AsyncExecutorProvider implements ExecutorServiceProvider {
29-
private final ThreadPoolSupplier executorServiceSupplier;
30-
31-
AsyncExecutorProvider(Config config) {
32-
this.executorServiceSupplier = ThreadPoolSupplier.builder()
33-
.corePoolSize(1)
34-
.maxPoolSize(10)
35-
.prestart(false)
36-
.threadNamePrefix("helidon-jersey-async")
37-
.config(config)
38-
.build();
30+
private final Supplier<ExecutorService> executorServiceSupplier;
31+
32+
AsyncExecutorProvider(Supplier<ExecutorService> supplier) {
33+
this.executorServiceSupplier = supplier;
34+
}
35+
36+
static ExecutorServiceProvider create(Config config) {
37+
return new AsyncExecutorProvider(ThreadPoolSupplier.builder()
38+
.corePoolSize(1)
39+
.maxPoolSize(10)
40+
.prestart(false)
41+
.threadNamePrefix("helidon-jersey-async")
42+
.config(config.get("async-executor-service"))
43+
.build());
44+
}
45+
46+
static ExecutorServiceProvider create(ExecutorService executor) {
47+
return new AsyncExecutorProvider(() -> executor);
3948
}
4049

4150
@Override

webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353

5454
import io.opentracing.Span;
5555
import io.opentracing.SpanContext;
56-
import org.eclipse.microprofile.config.ConfigProvider;
5756
import org.glassfish.jersey.internal.PropertiesDelegate;
5857
import org.glassfish.jersey.internal.util.collection.Ref;
5958
import org.glassfish.jersey.server.ApplicationHandler;
@@ -129,10 +128,20 @@ public class JerseySupport implements Service {
129128
* and Config
130129
*/
131130
private JerseySupport(Builder builder) {
132-
ExecutorService executorService = (builder.executorService != null) ? builder.executorService : getDefaultThreadPool();
131+
ExecutorService executorService = (builder.executorService != null)
132+
? builder.executorService
133+
: getDefaultThreadPool(builder.config);
133134
this.service = Contexts.wrap(executorService);
134135

135-
builder.resourceConfig.register(new AsyncExecutorProvider(builder.config));
136+
// make sure we have a wrapped async executor as well
137+
if (builder.asyncExecutorService == null) {
138+
// create a new one from configuration
139+
builder.resourceConfig.register(AsyncExecutorProvider.create(builder.config));
140+
} else {
141+
// use the one provided
142+
builder.resourceConfig.register(AsyncExecutorProvider.create(builder.asyncExecutorService));
143+
}
144+
136145
this.appHandler = new ApplicationHandler(builder.resourceConfig, new ServerBinder(executorService));
137146
this.container = new HelidonJerseyContainer(appHandler, builder.resourceConfig);
138147
}
@@ -143,11 +152,9 @@ public void update(Routing.Rules routingRules) {
143152
appHandler.onStartup(container);
144153
}
145154

146-
private static ExecutorService getDefaultThreadPool() {
155+
private static synchronized ExecutorService getDefaultThreadPool(Config config) {
147156
if (DEFAULT_THREAD_POOL.get() == null) {
148-
Config executorConfig = ((Config) ConfigProvider.getConfig())
149-
.get("server.executor-service");
150-
157+
Config executorConfig = config.get("executor-service");
151158
DEFAULT_THREAD_POOL.set(ServerThreadPoolSupplier.builder()
152159
.name("server")
153160
.config(executorConfig)
@@ -444,6 +451,7 @@ public static final class Builder implements Configurable<Builder>, io.helidon.c
444451
private ResourceConfig resourceConfig;
445452
private ExecutorService executorService;
446453
private Config config = Config.empty();
454+
private ExecutorService asyncExecutorService;
447455

448456
private Builder() {
449457
this(null);
@@ -549,6 +557,18 @@ public Builder executorService(ExecutorService executorService) {
549557
return this;
550558
}
551559

560+
/**
561+
* Sets the executor service to use for a handling of asynchronous requests
562+
* with {@link javax.ws.rs.container.AsyncResponse}.
563+
*
564+
* @param executorService the executor service to use for a handling of asynchronous requests
565+
* @return an updated instance
566+
*/
567+
public Builder asyncExecutorService(ExecutorService executorService) {
568+
this.asyncExecutorService = executorService;
569+
return this;
570+
}
571+
552572
/**
553573
* Update configuration from Config.
554574
* Currently used to set up async executor service only.

0 commit comments

Comments
 (0)