Skip to content

Commit a256cff

Browse files
author
Martin Littkovsky
committed
[io.net] Address review: set stop timeout at pool start, add regression test
Move setStopTimeout(0) directly after the explicit pool start - the old comment tied it to the HTTP client's start, which no longer starts the pool - so it also applies when common-client initialization fails later. Replace the 2-second continue-anyway workaround in the test's tearDown() with an assertion that deactivate() completes, and add a test pinning the ownership model: both clients share the pool unmanaged, and deactivate() itself stops it. Signed-off-by: Martin Littkovsky <2018turtle@proton.me>
1 parent 0e46566 commit a256cff

2 files changed

Lines changed: 32 additions & 9 deletions

File tree

bundles/org.openhab.core.io.net/src/main/java/org/openhab/core/io/net/http/internal/WebClientFactoryImpl.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ private synchronized void initialize() {
240240
// Jetty's ContainerLifeCycle javadoc). It is stopped explicitly in deactivate().
241241
try {
242242
threadPool.start();
243+
// Set the stop timeout right after starting the pool we now own. We need the
244+
// stop timeout in order to prevent blocking the deactivation of this
245+
// component, see https://github.com/eclipse/smarthome/issues/6632
246+
threadPool.setStopTimeout(0);
243247
} catch (Exception e) {
244248
// roll back so a later initialize() retry recreates the pool
245249
// instead of reusing a dead one
@@ -250,11 +254,6 @@ private synchronized void initialize() {
250254

251255
if (commonHttpClient == null) {
252256
commonHttpClient = createHttpClientInternal("common", null, true, threadPool);
253-
// we need to set the stop timeout AFTER the client has been started, because
254-
// otherwise the Jetty client sets it back to the default value.
255-
// We need the stop timeout in order to prevent blocking the deactivation of this
256-
// component, see https://github.com/eclipse/smarthome/issues/6632
257-
threadPool.setStopTimeout(0);
258257
logger.debug("Jetty shared http client created");
259258
}
260259

bundles/org.openhab.core.io.net/src/test/java/org/openhab/core/io/net/http/internal/WebClientFactoryImplTest.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Map;
2727
import java.util.concurrent.ArrayBlockingQueue;
2828
import java.util.concurrent.ExecutionException;
29+
import java.util.concurrent.Executor;
2930
import java.util.concurrent.ThreadPoolExecutor;
3031
import java.util.concurrent.TimeUnit;
3132
import java.util.concurrent.TimeoutException;
@@ -36,6 +37,7 @@
3637
import org.eclipse.jdt.annotation.NonNullByDefault;
3738
import org.eclipse.jetty.client.HttpClient;
3839
import org.eclipse.jetty.client.api.ContentResponse;
40+
import org.eclipse.jetty.util.thread.QueuedThreadPool;
3941
import org.eclipse.jetty.websocket.client.WebSocketClient;
4042
import org.junit.jupiter.api.AfterEach;
4143
import org.junit.jupiter.api.BeforeEach;
@@ -68,12 +70,14 @@ public void setup() {
6870

6971
@AfterEach
7072
public void tearDown() throws InterruptedException {
71-
// Sometimes a java.nio.channels.ClosedSelectorException occurs when the commonWebSocketClient
72-
// is stopped while its threads are still starting. This would cause webClientFactory.deactivate()
73-
// to block forever so continue if it has not completed after 2 seconds.
73+
// Regression check for the former shutdown hang: stopping the common HTTP client used to
74+
// tear down the shared thread pool underneath the common WebSocketClient, whose selector
75+
// then died with a ClosedSelectorException and deactivate() blocked forever.
76+
// Deactivation must always complete.
7477
Thread deactivateThread = new Thread(() -> webClientFactory.deactivate());
7578
deactivateThread.start();
76-
deactivateThread.join(2000);
79+
deactivateThread.join(10_000);
80+
assertThat("deactivate() did not complete", deactivateThread.isAlive(), is(false));
7781
}
7882

7983
@Test
@@ -85,6 +89,26 @@ public void testGetClients() throws Exception {
8589
assertThat(webSocketClient, is(notNullValue()));
8690
}
8791

92+
@Test
93+
public void testSharedThreadPoolIsUnmanagedAndStoppedByDeactivate() throws Exception {
94+
HttpClient httpClient = webClientFactory.getCommonHttpClient();
95+
WebSocketClient webSocketClient = webClientFactory.getCommonWebSocketClient();
96+
97+
Executor executor = httpClient.getExecutor();
98+
assertThat(executor, is(instanceOf(QueuedThreadPool.class)));
99+
QueuedThreadPool sharedPool = (QueuedThreadPool) executor;
100+
101+
// both clients use the same pool, but neither container manages its lifecycle
102+
assertThat(webSocketClient.getHttpClient().getExecutor(), is(sameInstance(executor)));
103+
assertThat(httpClient.isManaged(sharedPool), is(false));
104+
assertThat(webSocketClient.getHttpClient().isManaged(sharedPool), is(false));
105+
assertThat(sharedPool.isRunning(), is(true));
106+
107+
// deactivation stops the pool itself, after both clients
108+
webClientFactory.deactivate();
109+
assertThat(sharedPool.isRunning(), is(false));
110+
}
111+
88112
@Disabled("connecting to the outside world makes this test flaky")
89113
@Test
90114
public void testCommonClientUsesExtensibleTrustManager() throws Exception {

0 commit comments

Comments
 (0)