diff --git a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java index 316d316d8..8287d8bf6 100644 --- a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java +++ b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java @@ -106,6 +106,7 @@ public class JerseySupport implements Service { private final ExecutorService service; private final JerseyHandler handler = new JerseyHandler(); private final HelidonJerseyContainer container; + private final Thread serviceShutdownHook; /** * Creates a Jersey Support based on the provided JAX-RS application. @@ -121,6 +122,10 @@ public class JerseySupport implements Service { : getDefaultThreadPool(builder.config); this.service = Contexts.wrap(executorService); + // Prevents reads/writes after Netty event loops are shutdown + serviceShutdownHook = new Thread(service::shutdownNow); + Runtime.getRuntime().addShutdownHook(serviceShutdownHook); + // make sure we have a wrapped async executor as well if (builder.asyncExecutorService == null) { // create a new one from configuration @@ -140,6 +145,15 @@ public class JerseySupport implements Service { appHandler.onStartup(container); } + /** + * Returns registered shutdown hook for testing purposes. + * + * @return service shutdown hook + */ + Thread serviceShutdownHook() { + return serviceShutdownHook; + } + private static synchronized ExecutorService getDefaultThreadPool(Config config) { if (DEFAULT_THREAD_POOL.get() == null) { Config executorConfig = config.get("executor-service"); diff --git a/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportShutdownTest.java b/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportShutdownTest.java new file mode 100644 index 000000000..c68871d70 --- /dev/null +++ b/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportShutdownTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver.jersey; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.CoreMatchers.notNullValue; + +public class JerseySupportShutdownTest { + + @Test + public void shutdownHook() { + JerseySupport jerseySupport = JerseySupport.builder().build(); + assertThat(jerseySupport.serviceShutdownHook(), notNullValue()); + boolean exists = Runtime.getRuntime().removeShutdownHook(jerseySupport.serviceShutdownHook()); + assertThat(exists, is(true)); + } +}