diff options
Diffstat (limited to 'subprojects/language-web/src')
6 files changed, 84 insertions, 39 deletions
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java index 960eef71..f49f46ee 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java | |||
@@ -11,7 +11,6 @@ import org.eclipse.jetty.ee10.servlet.ServletHolder; | |||
11 | import org.eclipse.jetty.ee10.servlet.SessionHandler; | 11 | import org.eclipse.jetty.ee10.servlet.SessionHandler; |
12 | import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; | 12 | import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; |
13 | import org.eclipse.jetty.server.Server; | 13 | import org.eclipse.jetty.server.Server; |
14 | import org.eclipse.jetty.util.VirtualThreads; | ||
15 | import org.eclipse.jetty.util.resource.Resource; | 14 | import org.eclipse.jetty.util.resource.Resource; |
16 | import org.eclipse.jetty.util.resource.ResourceFactory; | 15 | import org.eclipse.jetty.util.resource.ResourceFactory; |
17 | import org.slf4j.Logger; | 16 | import org.slf4j.Logger; |
@@ -44,13 +43,7 @@ public class ServerLauncher { | |||
44 | private final Server server; | 43 | private final Server server; |
45 | 44 | ||
46 | public ServerLauncher(InetSocketAddress bindAddress, String[] allowedOrigins, String webSocketUrl) { | 45 | public ServerLauncher(InetSocketAddress bindAddress, String[] allowedOrigins, String webSocketUrl) { |
47 | server = new Server(bindAddress); | 46 | server = VirtualThreadUtils.newServerWithVirtualThreadsThreadPool("jetty", bindAddress); |
48 | enableVirtualThreads(server); | ||
49 | if (server.getThreadPool() instanceof VirtualThreads.Configurable virtualThreadsConfigurable) { | ||
50 | // Change this to setVirtualThreadsExecutor once | ||
51 | // https://github.com/eclipse/jetty.project/commit/83154b4ffe4767ef44981598d6c26e6a5d32e57c gets released. | ||
52 | virtualThreadsConfigurable.setUseVirtualThreads(VirtualThreads.areSupported()); | ||
53 | } | ||
54 | var handler = new ServletContextHandler(); | 47 | var handler = new ServletContextHandler(); |
55 | addSessionHandler(handler); | 48 | addSessionHandler(handler); |
56 | addProblemServlet(handler, allowedOrigins); | 49 | addProblemServlet(handler, allowedOrigins); |
@@ -150,14 +143,6 @@ public class ServerLauncher { | |||
150 | } | 143 | } |
151 | } | 144 | } |
152 | 145 | ||
153 | public static void enableVirtualThreads(Server server) { | ||
154 | if (server.getThreadPool() instanceof VirtualThreads.Configurable virtualThreadsConfigurable) { | ||
155 | // Change this to setVirtualThreadsExecutor once | ||
156 | // https://github.com/eclipse/jetty.project/commit/83154b4ffe4767ef44981598d6c26e6a5d32e57c gets released. | ||
157 | virtualThreadsConfigurable.setUseVirtualThreads(VirtualThreads.areSupported()); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | private static String getListenAddress() { | 146 | private static String getListenAddress() { |
162 | var listenAddress = System.getenv("LISTEN_ADDRESS"); | 147 | var listenAddress = System.getenv("LISTEN_ADDRESS"); |
163 | if (listenAddress == null) { | 148 | if (listenAddress == null) { |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java new file mode 100644 index 00000000..a055e755 --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java | |||
@@ -0,0 +1,52 @@ | |||
1 | package tools.refinery.language.web; | ||
2 | |||
3 | import org.eclipse.jetty.server.Server; | ||
4 | import org.eclipse.jetty.server.ServerConnector; | ||
5 | import org.eclipse.jetty.util.thread.QueuedThreadPool; | ||
6 | import org.eclipse.jetty.util.thread.ThreadPool; | ||
7 | |||
8 | import java.net.InetSocketAddress; | ||
9 | import java.time.Duration; | ||
10 | import java.util.concurrent.ExecutorService; | ||
11 | import java.util.concurrent.Executors; | ||
12 | |||
13 | public final class VirtualThreadUtils { | ||
14 | private VirtualThreadUtils() { | ||
15 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
16 | } | ||
17 | |||
18 | public static ExecutorService newNamedVirtualThreadsExecutor(String name) { | ||
19 | // Based on | ||
20 | // https://github.com/eclipse/jetty.project/blob/83154b4ffe4767ef44981598d6c26e6a5d32e57c/jetty-server/src/main/config/etc/jetty-threadpool-virtual-preview.xml | ||
21 | return Executors.newThreadPerTaskExecutor(Thread.ofVirtual() | ||
22 | .allowSetThreadLocals(true) | ||
23 | .inheritInheritableThreadLocals(false) | ||
24 | .name(name + "-virtual-", 0) | ||
25 | .factory()); | ||
26 | } | ||
27 | |||
28 | public static ThreadPool newThreadPoolWithVirtualThreadsExecutor(String name) { | ||
29 | // Based on | ||
30 | // https://github.com/eclipse/jetty.project/blob/83154b4ffe4767ef44981598d6c26e6a5d32e57c/jetty-server/src/main/config/etc/jetty-threadpool-virtual-preview.xml | ||
31 | int timeout = (int) Duration.ofMinutes(1).toMillis(); | ||
32 | var threadPool = new QueuedThreadPool(200, 10, timeout, -1, null, null); | ||
33 | threadPool.setName(name); | ||
34 | threadPool.setDetailedDump(false); | ||
35 | threadPool.setVirtualThreadsExecutor(newNamedVirtualThreadsExecutor(name)); | ||
36 | return threadPool; | ||
37 | } | ||
38 | |||
39 | public static Server newServerWithVirtualThreadsThreadPool(String name, InetSocketAddress listenAddress) { | ||
40 | var server = new Server(newThreadPoolWithVirtualThreadsExecutor(name)); | ||
41 | var connector = new ServerConnector(server); | ||
42 | try { | ||
43 | connector.setHost(listenAddress.getHostName()); | ||
44 | connector.setPort(listenAddress.getPort()); | ||
45 | server.addConnector(connector); | ||
46 | } catch (Exception e) { | ||
47 | connector.close(); | ||
48 | throw e; | ||
49 | } | ||
50 | return server; | ||
51 | } | ||
52 | } | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java index ead98927..abbcbd53 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java | |||
@@ -1,9 +1,9 @@ | |||
1 | package tools.refinery.language.web.xtext; | 1 | package tools.refinery.language.web.xtext; |
2 | 2 | ||
3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | 3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; |
4 | import tools.refinery.language.web.VirtualThreadUtils; | ||
4 | 5 | ||
5 | import java.util.concurrent.ExecutorService; | 6 | import java.util.concurrent.ExecutorService; |
6 | import java.util.concurrent.Executors; | ||
7 | 7 | ||
8 | public class VirtualThreadExecutorServiceProvider extends ExecutorServiceProvider { | 8 | public class VirtualThreadExecutorServiceProvider extends ExecutorServiceProvider { |
9 | private static final String THREAD_POOL_NAME = "xtextWeb"; | 9 | private static final String THREAD_POOL_NAME = "xtextWeb"; |
@@ -11,10 +11,6 @@ public class VirtualThreadExecutorServiceProvider extends ExecutorServiceProvide | |||
11 | @Override | 11 | @Override |
12 | protected ExecutorService createInstance(String key) { | 12 | protected ExecutorService createInstance(String key) { |
13 | var name = key == null ? THREAD_POOL_NAME : THREAD_POOL_NAME + "-" + key; | 13 | var name = key == null ? THREAD_POOL_NAME : THREAD_POOL_NAME + "-" + key; |
14 | return Executors.newThreadPerTaskExecutor(Thread.ofVirtual() | 14 | return VirtualThreadUtils.newNamedVirtualThreadsExecutor(name); |
15 | .allowSetThreadLocals(true) | ||
16 | .inheritInheritableThreadLocals(false) | ||
17 | .name(name + "-", 0) | ||
18 | .factory()); | ||
19 | } | 15 | } |
20 | } | 16 | } |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java index 24fab4e3..ecbefc4f 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java | |||
@@ -14,9 +14,7 @@ import org.eclipse.jetty.http.HttpStatus; | |||
14 | import org.eclipse.jetty.server.Server; | 14 | import org.eclipse.jetty.server.Server; |
15 | import org.eclipse.xtext.testing.GlobalRegistries; | 15 | import org.eclipse.xtext.testing.GlobalRegistries; |
16 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; | 16 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; |
17 | import org.junit.jupiter.api.AfterEach; | 17 | import org.junit.jupiter.api.*; |
18 | import org.junit.jupiter.api.BeforeEach; | ||
19 | import org.junit.jupiter.api.Test; | ||
20 | import org.junit.jupiter.params.ParameterizedTest; | 18 | import org.junit.jupiter.params.ParameterizedTest; |
21 | import org.junit.jupiter.params.provider.ValueSource; | 19 | import org.junit.jupiter.params.provider.ValueSource; |
22 | import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; | 20 | import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; |
@@ -25,6 +23,7 @@ import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; | |||
25 | 23 | ||
26 | import java.io.IOException; | 24 | import java.io.IOException; |
27 | import java.net.InetSocketAddress; | 25 | import java.net.InetSocketAddress; |
26 | import java.net.ServerSocket; | ||
28 | import java.net.URI; | 27 | import java.net.URI; |
29 | import java.util.concurrent.CompletableFuture; | 28 | import java.util.concurrent.CompletableFuture; |
30 | import java.util.concurrent.CompletionException; | 29 | import java.util.concurrent.CompletionException; |
@@ -34,18 +33,29 @@ import static org.hamcrest.Matchers.*; | |||
34 | import static org.junit.jupiter.api.Assertions.assertThrows; | 33 | import static org.junit.jupiter.api.Assertions.assertThrows; |
35 | 34 | ||
36 | class ProblemWebSocketServletIntegrationTest { | 35 | class ProblemWebSocketServletIntegrationTest { |
37 | private static final int SERVER_PORT = 28080; | 36 | private static final String HOSTNAME = "127.0.0.1"; |
38 | 37 | ||
39 | private static final String SERVLET_URI = "/xtext-service"; | 38 | private static final String SERVLET_URI = "/xtext-service"; |
40 | 39 | ||
41 | private GlobalStateMemento stateBeforeInjectorCreation; | 40 | private GlobalStateMemento stateBeforeInjectorCreation; |
42 | 41 | ||
42 | private TestInfo testInfo; | ||
43 | |||
44 | private int serverPort; | ||
45 | |||
43 | private Server server; | 46 | private Server server; |
44 | 47 | ||
45 | private WebSocketClient client; | 48 | private WebSocketClient client; |
46 | 49 | ||
47 | @BeforeEach | 50 | @BeforeEach |
48 | void beforeEach() throws Exception { | 51 | void beforeEach(TestInfo testInfo) throws Exception { |
52 | this.testInfo = testInfo; | ||
53 | // Find a free port for running the test. See e.g., https://stackoverflow.com/a/65937797 | ||
54 | try (var serverSocket = new ServerSocket()) { | ||
55 | serverSocket.setReuseAddress(true); | ||
56 | serverSocket.bind(new InetSocketAddress(HOSTNAME, 0)); | ||
57 | serverPort = serverSocket.getLocalPort(); | ||
58 | } | ||
49 | stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); | 59 | stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); |
50 | client = new WebSocketClient(); | 60 | client = new WebSocketClient(); |
51 | client.start(); | 61 | client.start(); |
@@ -147,7 +157,7 @@ class ProblemWebSocketServletIntegrationTest { | |||
147 | } | 157 | } |
148 | } | 158 | } |
149 | 159 | ||
150 | @ParameterizedTest(name = "Origin: {0}") | 160 | @ParameterizedTest(name = "validOriginTest(\"{0}\")") |
151 | @ValueSource(strings = { "https://refinery.example", "https://refinery.example:443", "HTTPS://REFINERY.EXAMPLE" }) | 161 | @ValueSource(strings = { "https://refinery.example", "https://refinery.example:443", "HTTPS://REFINERY.EXAMPLE" }) |
152 | void validOriginTest(String origin) { | 162 | void validOriginTest(String origin) { |
153 | startServer("https://refinery.example,https://refinery.example:443"); | 163 | startServer("https://refinery.example,https://refinery.example:443"); |
@@ -176,8 +186,9 @@ class ProblemWebSocketServletIntegrationTest { | |||
176 | } | 186 | } |
177 | 187 | ||
178 | private void startServer(String allowedOrigins) { | 188 | private void startServer(String allowedOrigins) { |
179 | server = new Server(new InetSocketAddress(SERVER_PORT)); | 189 | var testName = getClass().getSimpleName() + "-" + testInfo.getDisplayName(); |
180 | ServerLauncher.enableVirtualThreads(server); | 190 | var listenAddress = new InetSocketAddress(HOSTNAME, serverPort); |
191 | server = VirtualThreadUtils.newServerWithVirtualThreadsThreadPool(testName, listenAddress); | ||
181 | var handler = new ServletContextHandler(); | 192 | var handler = new ServletContextHandler(); |
182 | var holder = new ServletHolder(ProblemWebSocketServlet.class); | 193 | var holder = new ServletHolder(ProblemWebSocketServlet.class); |
183 | if (allowedOrigins != null) { | 194 | if (allowedOrigins != null) { |
@@ -201,7 +212,8 @@ class ProblemWebSocketServletIntegrationTest { | |||
201 | upgradeRequest.setSubProtocols(subProtocols); | 212 | upgradeRequest.setSubProtocols(subProtocols); |
202 | CompletableFuture<Session> sessionFuture; | 213 | CompletableFuture<Session> sessionFuture; |
203 | try { | 214 | try { |
204 | sessionFuture = client.connect(webSocketClient, URI.create("ws://localhost:" + SERVER_PORT + SERVLET_URI), | 215 | sessionFuture = client.connect(webSocketClient, |
216 | URI.create("ws://%s:%d%s".formatted(HOSTNAME, serverPort, SERVLET_URI)), | ||
205 | upgradeRequest); | 217 | upgradeRequest); |
206 | } catch (IOException e) { | 218 | } catch (IOException e) { |
207 | throw new AssertionError("Unexpected exception while connection to websocket", e); | 219 | throw new AssertionError("Unexpected exception while connection to websocket", e); |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java index 25343109..c634e8fc 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java | |||
@@ -13,7 +13,7 @@ public class AwaitTerminationExecutorServiceProvider extends VirtualThreadExecut | |||
13 | 13 | ||
14 | @Override | 14 | @Override |
15 | protected ExecutorService createInstance(String key) { | 15 | protected ExecutorService createInstance(String key) { |
16 | var instance = new RestartableCachedThreadPool(); | 16 | var instance = new RestartableCachedThreadPool(() -> super.createInstance(key)); |
17 | synchronized (servicesToShutDown) { | 17 | synchronized (servicesToShutDown) { |
18 | servicesToShutDown.add(instance); | 18 | servicesToShutDown.add(instance); |
19 | } | 19 | } |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java index a8655313..cf805eda 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java | |||
@@ -1,5 +1,6 @@ | |||
1 | package tools.refinery.language.web.tests; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import com.google.inject.Provider; | ||
3 | import org.jetbrains.annotations.NotNull; | 4 | import org.jetbrains.annotations.NotNull; |
4 | import org.slf4j.Logger; | 5 | import org.slf4j.Logger; |
5 | import org.slf4j.LoggerFactory; | 6 | import org.slf4j.LoggerFactory; |
@@ -13,14 +14,17 @@ public class RestartableCachedThreadPool implements ExecutorService { | |||
13 | 14 | ||
14 | private ExecutorService delegate; | 15 | private ExecutorService delegate; |
15 | 16 | ||
16 | public RestartableCachedThreadPool() { | 17 | private final Provider<ExecutorService> executorServiceProvider; |
17 | delegate = createExecutorService(); | 18 | |
19 | public RestartableCachedThreadPool(Provider<ExecutorService> executorServiceProvider) { | ||
20 | this.executorServiceProvider = executorServiceProvider; | ||
21 | delegate = executorServiceProvider.get(); | ||
18 | } | 22 | } |
19 | 23 | ||
20 | public void waitForAllTasksToFinish() { | 24 | public void waitForAllTasksToFinish() { |
21 | delegate.shutdown(); | 25 | delegate.shutdown(); |
22 | waitForTermination(); | 26 | waitForTermination(); |
23 | delegate = createExecutorService(); | 27 | delegate = executorServiceProvider.get(); |
24 | } | 28 | } |
25 | 29 | ||
26 | public void waitForTermination() { | 30 | public void waitForTermination() { |
@@ -35,10 +39,6 @@ public class RestartableCachedThreadPool implements ExecutorService { | |||
35 | } | 39 | } |
36 | } | 40 | } |
37 | 41 | ||
38 | protected ExecutorService createExecutorService() { | ||
39 | return Executors.newCachedThreadPool(); | ||
40 | } | ||
41 | |||
42 | @Override | 42 | @Override |
43 | public boolean awaitTermination(long arg0, @NotNull TimeUnit arg1) throws InterruptedException { | 43 | public boolean awaitTermination(long arg0, @NotNull TimeUnit arg1) throws InterruptedException { |
44 | return delegate.awaitTermination(arg0, arg1); | 44 | return delegate.awaitTermination(arg0, arg1); |