aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-12-10 14:32:17 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-12-12 16:31:04 +0100
commitd786792fcf32eb5d94c870a83020941b8693deb1 (patch)
treeea8b5a57468cb24f864859639bb537d46354823f /subprojects/language-web
parentrefactor(frontend): split vite config (diff)
downloadrefinery-d786792fcf32eb5d94c870a83020941b8693deb1.tar.gz
refinery-d786792fcf32eb5d94c870a83020941b8693deb1.tar.zst
refinery-d786792fcf32eb5d94c870a83020941b8693deb1.zip
chore(web): upgrade to Jetty 12.0.0.alpha3
Also refactor virtual thread ExecutorService handling and integration tests.
Diffstat (limited to 'subprojects/language-web')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java17
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java52
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java8
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java30
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java2
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java14
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;
11import org.eclipse.jetty.ee10.servlet.SessionHandler; 11import org.eclipse.jetty.ee10.servlet.SessionHandler;
12import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; 12import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
13import org.eclipse.jetty.server.Server; 13import org.eclipse.jetty.server.Server;
14import org.eclipse.jetty.util.VirtualThreads;
15import org.eclipse.jetty.util.resource.Resource; 14import org.eclipse.jetty.util.resource.Resource;
16import org.eclipse.jetty.util.resource.ResourceFactory; 15import org.eclipse.jetty.util.resource.ResourceFactory;
17import org.slf4j.Logger; 16import 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 @@
1package tools.refinery.language.web;
2
3import org.eclipse.jetty.server.Server;
4import org.eclipse.jetty.server.ServerConnector;
5import org.eclipse.jetty.util.thread.QueuedThreadPool;
6import org.eclipse.jetty.util.thread.ThreadPool;
7
8import java.net.InetSocketAddress;
9import java.time.Duration;
10import java.util.concurrent.ExecutorService;
11import java.util.concurrent.Executors;
12
13public 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 @@
1package tools.refinery.language.web.xtext; 1package tools.refinery.language.web.xtext;
2 2
3import org.eclipse.xtext.ide.ExecutorServiceProvider; 3import org.eclipse.xtext.ide.ExecutorServiceProvider;
4import tools.refinery.language.web.VirtualThreadUtils;
4 5
5import java.util.concurrent.ExecutorService; 6import java.util.concurrent.ExecutorService;
6import java.util.concurrent.Executors;
7 7
8public class VirtualThreadExecutorServiceProvider extends ExecutorServiceProvider { 8public 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;
14import org.eclipse.jetty.server.Server; 14import org.eclipse.jetty.server.Server;
15import org.eclipse.xtext.testing.GlobalRegistries; 15import org.eclipse.xtext.testing.GlobalRegistries;
16import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; 16import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento;
17import org.junit.jupiter.api.AfterEach; 17import org.junit.jupiter.api.*;
18import org.junit.jupiter.api.BeforeEach;
19import org.junit.jupiter.api.Test;
20import org.junit.jupiter.params.ParameterizedTest; 18import org.junit.jupiter.params.ParameterizedTest;
21import org.junit.jupiter.params.provider.ValueSource; 19import org.junit.jupiter.params.provider.ValueSource;
22import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; 20import tools.refinery.language.web.tests.WebSocketIntegrationTestClient;
@@ -25,6 +23,7 @@ import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet;
25 23
26import java.io.IOException; 24import java.io.IOException;
27import java.net.InetSocketAddress; 25import java.net.InetSocketAddress;
26import java.net.ServerSocket;
28import java.net.URI; 27import java.net.URI;
29import java.util.concurrent.CompletableFuture; 28import java.util.concurrent.CompletableFuture;
30import java.util.concurrent.CompletionException; 29import java.util.concurrent.CompletionException;
@@ -34,18 +33,29 @@ import static org.hamcrest.Matchers.*;
34import static org.junit.jupiter.api.Assertions.assertThrows; 33import static org.junit.jupiter.api.Assertions.assertThrows;
35 34
36class ProblemWebSocketServletIntegrationTest { 35class 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 @@
1package tools.refinery.language.web.tests; 1package tools.refinery.language.web.tests;
2 2
3import com.google.inject.Provider;
3import org.jetbrains.annotations.NotNull; 4import org.jetbrains.annotations.NotNull;
4import org.slf4j.Logger; 5import org.slf4j.Logger;
5import org.slf4j.LoggerFactory; 6import 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);