diff options
author | 2023-09-05 02:20:57 +0200 | |
---|---|---|
committer | 2023-09-05 02:21:59 +0200 | |
commit | 72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f (patch) | |
tree | 186139d92c8851ccf1f568af9a41a02f8b860b1d | |
parent | fix(frontend): GraphArea scroll (diff) | |
download | refinery-72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f.tar.gz refinery-72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f.tar.zst refinery-72a3f0f50560d70f2f2ff3e1ab6dbb92f80a5f0f.zip |
feat(web): control server settings with env vars
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 | */ |
10 | package tools.refinery.language.web; | 10 | package tools.refinery.language.web; |
11 | 11 | ||
12 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
12 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; | 13 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; |
13 | import org.eclipse.xtext.web.server.model.IWebDocumentProvider; | 14 | import org.eclipse.xtext.web.server.model.IWebDocumentProvider; |
14 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; | 15 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; |
15 | import org.eclipse.xtext.web.server.occurrences.OccurrencesService; | 16 | import org.eclipse.xtext.web.server.occurrences.OccurrencesService; |
16 | import tools.refinery.language.web.occurrences.ProblemOccurrencesService; | 17 | import tools.refinery.language.web.occurrences.ProblemOccurrencesService; |
18 | import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider; | ||
17 | import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; | 19 | import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; |
18 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; | 20 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; |
19 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; | 21 | import 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; | |||
22 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; | 22 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; |
23 | 23 | ||
24 | import java.util.List; | 24 | import java.util.List; |
25 | import java.util.Optional; | ||
25 | import java.util.concurrent.*; | 26 | import java.util.concurrent.*; |
27 | import java.util.concurrent.atomic.AtomicBoolean; | ||
26 | 28 | ||
27 | @Singleton | 29 | @Singleton |
28 | public class SemanticsService extends AbstractCachedService<SemanticsResult> { | 30 | public 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 | */ | ||
6 | package tools.refinery.language.web.xtext.server; | ||
7 | |||
8 | import com.google.inject.Singleton; | ||
9 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
10 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; | ||
11 | import org.jetbrains.annotations.NotNull; | ||
12 | import tools.refinery.language.web.semantics.SemanticsService; | ||
13 | |||
14 | import java.lang.invoke.MethodHandle; | ||
15 | import java.lang.invoke.MethodHandles; | ||
16 | import java.util.Optional; | ||
17 | import java.util.concurrent.ExecutorService; | ||
18 | import java.util.concurrent.Executors; | ||
19 | import java.util.concurrent.ThreadFactory; | ||
20 | import java.util.concurrent.atomic.AtomicInteger; | ||
21 | |||
22 | @Singleton | ||
23 | public 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; |