aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-05 02:20:57 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-05 02:21:59 +0200
commit72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f (patch)
tree186139d92c8851ccf1f568af9a41a02f8b860b1d /subprojects/language-web
parentfix(frontend): GraphArea scroll (diff)
downloadrefinery-72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f.tar.gz
refinery-72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f.tar.zst
refinery-72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f.zip
feat(web): control server settings with env vars
Diffstat (limited to 'subprojects/language-web')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java6
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java4
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java31
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java110
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java1
5 files changed, 148 insertions, 4 deletions
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java
index b0197c01..6a6e0107 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java
@@ -9,11 +9,13 @@
9 */ 9 */
10package tools.refinery.language.web; 10package tools.refinery.language.web;
11 11
12import org.eclipse.xtext.ide.ExecutorServiceProvider;
12import org.eclipse.xtext.web.server.XtextServiceDispatcher; 13import org.eclipse.xtext.web.server.XtextServiceDispatcher;
13import org.eclipse.xtext.web.server.model.IWebDocumentProvider; 14import org.eclipse.xtext.web.server.model.IWebDocumentProvider;
14import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; 15import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
15import org.eclipse.xtext.web.server.occurrences.OccurrencesService; 16import org.eclipse.xtext.web.server.occurrences.OccurrencesService;
16import tools.refinery.language.web.occurrences.ProblemOccurrencesService; 17import tools.refinery.language.web.occurrences.ProblemOccurrencesService;
18import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider;
17import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; 19import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher;
18import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; 20import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess;
19import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; 21import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider;
@@ -37,4 +39,8 @@ public class ProblemWebModule extends AbstractProblemWebModule {
37 public Class<? extends OccurrencesService> bindOccurrencesService() { 39 public Class<? extends OccurrencesService> bindOccurrencesService() {
38 return ProblemOccurrencesService.class; 40 return ProblemOccurrencesService.class;
39 } 41 }
42
43 public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() {
44 return ThreadPoolExecutorServiceProvider.class;
45 }
40} 46}
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 d633b3fc..155efc6f 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
@@ -165,7 +165,7 @@ public class ServerLauncher {
165 private static int getListenPort() { 165 private static int getListenPort() {
166 var portStr = System.getenv("REFINERY_LISTEN_PORT"); 166 var portStr = System.getenv("REFINERY_LISTEN_PORT");
167 if (portStr != null) { 167 if (portStr != null) {
168 return Integer.parseInt(portStr); 168 return Integer.parseUnsignedInt(portStr);
169 } 169 }
170 return DEFAULT_LISTEN_PORT; 170 return DEFAULT_LISTEN_PORT;
171 } 171 }
@@ -187,7 +187,7 @@ public class ServerLauncher {
187 private static int getPublicPort() { 187 private static int getPublicPort() {
188 var portStr = System.getenv("REFINERY_PUBLIC_PORT"); 188 var portStr = System.getenv("REFINERY_PUBLIC_PORT");
189 if (portStr != null) { 189 if (portStr != null) {
190 return Integer.parseInt(portStr); 190 return Integer.parseUnsignedInt(portStr);
191 } 191 }
192 return DEFAULT_PUBLIC_PORT; 192 return DEFAULT_PUBLIC_PORT;
193 } 193 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
index ba55dc77..26924f0a 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -22,10 +22,14 @@ import tools.refinery.language.model.problem.Problem;
22import tools.refinery.language.web.xtext.server.push.PushWebDocument; 22import tools.refinery.language.web.xtext.server.push.PushWebDocument;
23 23
24import java.util.List; 24import java.util.List;
25import java.util.Optional;
25import java.util.concurrent.*; 26import java.util.concurrent.*;
27import java.util.concurrent.atomic.AtomicBoolean;
26 28
27@Singleton 29@Singleton
28public class SemanticsService extends AbstractCachedService<SemanticsResult> { 30public class SemanticsService extends AbstractCachedService<SemanticsResult> {
31 public static final String SEMANTICS_EXECUTOR = "semantics";
32
29 private static final Logger LOG = LoggerFactory.getLogger(SemanticsService.class); 33 private static final Logger LOG = LoggerFactory.getLogger(SemanticsService.class);
30 34
31 @Inject 35 @Inject
@@ -39,9 +43,24 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
39 43
40 private ExecutorService executorService; 44 private ExecutorService executorService;
41 45
46 private final long timeoutMs;
47
48 private final long warmupTimeoutMs;
49
50 private final AtomicBoolean warmedUp = new AtomicBoolean(false);
51
52 public SemanticsService() {
53 timeoutMs = getTimeout("REFINERY_SEMANTICS_TIMEOUT_MS").orElse(1000L);
54 warmupTimeoutMs = getTimeout("REFINERY_SEMANTICS_WARMUP_TIMEOUT_MS").orElse(timeoutMs * 2);
55 }
56
57 private static Optional<Long> getTimeout(String name) {
58 return Optional.ofNullable(System.getenv(name)).map(Long::parseUnsignedLong);
59 }
60
42 @Inject 61 @Inject
43 public void setExecutorServiceProvider(ExecutorServiceProvider provider) { 62 public void setExecutorServiceProvider(ExecutorServiceProvider provider) {
44 executorService = provider.get(this.getClass().getName()); 63 executorService = provider.get(SEMANTICS_EXECUTOR);
45 } 64 }
46 65
47 @Override 66 @Override
@@ -60,9 +79,14 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
60 var worker = workerProvider.get(); 79 var worker = workerProvider.get();
61 worker.setProblem(problem, cancelIndicator); 80 worker.setProblem(problem, cancelIndicator);
62 var future = executorService.submit(worker); 81 var future = executorService.submit(worker);
82 boolean warmedUpCurrently = warmedUp.get();
83 long timeout = warmedUpCurrently ? timeoutMs : warmupTimeoutMs;
63 SemanticsResult result = null; 84 SemanticsResult result = null;
64 try { 85 try {
65 result = future.get(2, TimeUnit.SECONDS); 86 result = future.get(timeout, TimeUnit.MILLISECONDS);
87 if (!warmedUpCurrently) {
88 warmedUp.set(true);
89 }
66 } catch (InterruptedException e) { 90 } catch (InterruptedException e) {
67 future.cancel(true); 91 future.cancel(true);
68 LOG.error("Semantics service interrupted", e); 92 LOG.error("Semantics service interrupted", e);
@@ -80,6 +104,9 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
80 return new SemanticsInternalErrorResult(message); 104 return new SemanticsInternalErrorResult(message);
81 } catch (TimeoutException e) { 105 } catch (TimeoutException e) {
82 future.cancel(true); 106 future.cancel(true);
107 if (!warmedUpCurrently) {
108 warmedUp.set(true);
109 }
83 LOG.trace("Semantics service timeout", e); 110 LOG.trace("Semantics service timeout", e);
84 return new SemanticsInternalErrorResult("Partial interpretation timed out"); 111 return new SemanticsInternalErrorResult("Partial interpretation timed out");
85 } 112 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
new file mode 100644
index 00000000..ba26ff58
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
@@ -0,0 +1,110 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.xtext.server;
7
8import com.google.inject.Singleton;
9import org.eclipse.xtext.ide.ExecutorServiceProvider;
10import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
11import org.jetbrains.annotations.NotNull;
12import tools.refinery.language.web.semantics.SemanticsService;
13
14import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles;
16import java.util.Optional;
17import java.util.concurrent.ExecutorService;
18import java.util.concurrent.Executors;
19import java.util.concurrent.ThreadFactory;
20import java.util.concurrent.atomic.AtomicInteger;
21
22@Singleton
23public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
24 private static final String DOCUMENT_LOCK_EXECUTOR;
25 private static final AtomicInteger POOL_ID = new AtomicInteger(1);
26
27 private final int executorThreadCount;
28 private final int lockExecutorThreadCount;
29 private final int semanticsExecutorThreadCount;
30
31 static {
32 var lookup = MethodHandles.lookup();
33 MethodHandle getter;
34 try {
35 var privateLookup = MethodHandles.privateLookupIn(XtextWebDocumentAccess.class, lookup);
36 getter = privateLookup.findStaticGetter(XtextWebDocumentAccess.class, "DOCUMENT_LOCK_EXECUTOR",
37 String.class);
38 } catch (IllegalAccessException | NoSuchFieldException e) {
39 throw new IllegalStateException("Failed to find getter", e);
40 }
41 try {
42 DOCUMENT_LOCK_EXECUTOR = (String) getter.invokeExact();
43 } catch (Error e) {
44 // Rethrow JVM errors.
45 throw e;
46 } catch (Throwable e) {
47 throw new IllegalStateException("Failed to get DOCUMENT_LOCK_EXECUTOR", e);
48 }
49 }
50
51 public ThreadPoolExecutorServiceProvider() {
52 executorThreadCount = getCount("REFINERY_XTEXT_THREAD_COUNT").orElse(0);
53 lockExecutorThreadCount = getCount("REFINERY_XTEXT_LOCKING_THREAD_COUNT").orElse(executorThreadCount);
54 semanticsExecutorThreadCount = getCount("REFINERY_XTEXT_SEMANTICS_THREAD_COUNT").orElse(executorThreadCount);
55 }
56
57 private static Optional<Integer> getCount(String name) {
58 return Optional.ofNullable(System.getenv(name)).map(Integer::parseUnsignedInt);
59 }
60
61 @Override
62 protected ExecutorService createInstance(String key) {
63 String name = "xtext-" + POOL_ID.getAndIncrement();
64 if (key != null) {
65 name = name + key + "-";
66 }
67 var threadFactory = new Factory(name, 5);
68 int size = getSize(key);
69 if (size == 0) {
70 return Executors.newCachedThreadPool(threadFactory);
71 }
72 return Executors.newFixedThreadPool(size, threadFactory);
73 }
74
75 private int getSize(String key) {
76 if (SemanticsService.SEMANTICS_EXECUTOR.equals(key)) {
77 return semanticsExecutorThreadCount;
78 } else if (DOCUMENT_LOCK_EXECUTOR.equals(key)) {
79 return lockExecutorThreadCount;
80 } else {
81 return executorThreadCount;
82 }
83 }
84
85 private static class Factory implements ThreadFactory {
86 // We have to explicitly store the {@link ThreadGroup} to create a {@link ThreadFactory}.
87 @SuppressWarnings("squid:S3014")
88 private final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
89 private final AtomicInteger threadId = new AtomicInteger(1);
90 private final String namePrefix;
91 private final int priority;
92
93 public Factory(String name, int priority) {
94 namePrefix = name + "-thread-";
95 this.priority = priority;
96 }
97
98 @Override
99 public Thread newThread(@NotNull Runnable runnable) {
100 var thread = new Thread(threadGroup, runnable, namePrefix + threadId.getAndIncrement());
101 if (thread.isDaemon()) {
102 thread.setDaemon(false);
103 }
104 if (thread.getPriority() != priority) {
105 thread.setPriority(priority);
106 }
107 return thread;
108 }
109 }
110}
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
index 4a5eed95..e9d889c4 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
@@ -34,6 +34,7 @@ public class ProblemWebInjectorProvider extends ProblemInjectorProvider {
34 // the tasks in the service and the {@link 34 // the tasks in the service and the {@link
35 // org.eclipse.xtext.testing.extensions.InjectionExtension}. 35 // org.eclipse.xtext.testing.extensions.InjectionExtension}.
36 return new ProblemWebModule() { 36 return new ProblemWebModule() {
37 @Override
37 @SuppressWarnings("unused") 38 @SuppressWarnings("unused")
38 public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() { 39 public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() {
39 return AwaitTerminationExecutorServiceProvider.class; 40 return AwaitTerminationExecutorServiceProvider.class;