aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java158
1 files changed, 158 insertions, 0 deletions
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..ff8f4943
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
@@ -0,0 +1,158 @@
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 org.slf4j.Logger;
13import org.slf4j.LoggerFactory;
14import tools.refinery.language.web.generator.ModelGenerationService;
15import tools.refinery.language.web.semantics.SemanticsService;
16
17import java.lang.invoke.MethodHandle;
18import java.lang.invoke.MethodHandles;
19import java.util.Collections;
20import java.util.HashMap;
21import java.util.Map;
22import java.util.Optional;
23import java.util.concurrent.ExecutorService;
24import java.util.concurrent.Executors;
25import java.util.concurrent.ScheduledExecutorService;
26import java.util.concurrent.ThreadFactory;
27import java.util.concurrent.atomic.AtomicInteger;
28
29@Singleton
30public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
31 private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolExecutorServiceProvider.class);
32 private static final String DOCUMENT_LOCK_EXECUTOR;
33 private static final AtomicInteger POOL_ID = new AtomicInteger(1);
34
35 private final Map<String, ScheduledExecutorService> scheduledInstanceCache =
36 Collections.synchronizedMap(new HashMap<>());
37 private final int executorThreadCount;
38 private final int lockExecutorThreadCount;
39 private final int semanticsExecutorThreadCount;
40 private final int generatorExecutorThreadCount;
41
42 static {
43 var lookup = MethodHandles.lookup();
44 MethodHandle getter;
45 try {
46 var privateLookup = MethodHandles.privateLookupIn(XtextWebDocumentAccess.class, lookup);
47 getter = privateLookup.findStaticGetter(XtextWebDocumentAccess.class, "DOCUMENT_LOCK_EXECUTOR",
48 String.class);
49 } catch (IllegalAccessException | NoSuchFieldException e) {
50 throw new IllegalStateException("Failed to find getter", e);
51 }
52 try {
53 DOCUMENT_LOCK_EXECUTOR = (String) getter.invokeExact();
54 } catch (Error e) {
55 // Rethrow JVM errors.
56 throw e;
57 } catch (Throwable e) {
58 throw new IllegalStateException("Failed to get DOCUMENT_LOCK_EXECUTOR", e);
59 }
60 }
61
62 public ThreadPoolExecutorServiceProvider() {
63 executorThreadCount = getCount("REFINERY_XTEXT_THREAD_COUNT").orElse(0);
64 lockExecutorThreadCount = getCount("REFINERY_XTEXT_LOCKING_THREAD_COUNT").orElse(executorThreadCount);
65 int semanticsCount = getCount("REFINERY_XTEXT_SEMANTICS_THREAD_COUNT").orElse(0);
66 if (semanticsCount == 0 || executorThreadCount == 0) {
67 semanticsExecutorThreadCount = 0;
68 } else {
69 semanticsExecutorThreadCount = Math.max(semanticsCount, executorThreadCount);
70 }
71 if (semanticsExecutorThreadCount != semanticsCount) {
72 LOG.warn("Setting REFINERY_XTEXT_SEMANTICS_THREAD_COUNT to {} to avoid deadlock. This value must be " +
73 "either 0 or at least as large as REFINERY_XTEXT_THREAD_COUNT to avoid lock contention.",
74 semanticsExecutorThreadCount);
75 }
76 generatorExecutorThreadCount = getCount("REFINERY_MODEL_GENERATION_THREAD_COUNT").orElse(executorThreadCount);
77 }
78
79 private static Optional<Integer> getCount(String name) {
80 return Optional.ofNullable(System.getenv(name)).map(Integer::parseUnsignedInt);
81 }
82
83 public ScheduledExecutorService getScheduled(String key) {
84 return scheduledInstanceCache.computeIfAbsent(key, this::createScheduledInstance);
85 }
86
87 @Override
88 protected ExecutorService createInstance(String key) {
89 String name = "xtext-" + POOL_ID.getAndIncrement();
90 if (key != null) {
91 name = name + "-" + key;
92 }
93 var threadFactory = new Factory(name, 5);
94 int size = getSize(key);
95 if (size == 0) {
96 return Executors.newCachedThreadPool(threadFactory);
97 }
98 return Executors.newFixedThreadPool(size, threadFactory);
99 }
100
101 protected ScheduledExecutorService createScheduledInstance(String key) {
102 String name = "xtext-scheduled-" + POOL_ID.getAndIncrement();
103 if (key != null) {
104 name = name + "-" + key;
105 }
106 var threadFactory = new Factory(name, 5);
107 return Executors.newScheduledThreadPool(1, threadFactory);
108 }
109
110 private int getSize(String key) {
111 if (SemanticsService.SEMANTICS_EXECUTOR.equals(key)) {
112 return semanticsExecutorThreadCount;
113 } else if (ModelGenerationService.MODEL_GENERATION_EXECUTOR.equals(key)) {
114 return generatorExecutorThreadCount;
115 } else if (DOCUMENT_LOCK_EXECUTOR.equals(key)) {
116 return lockExecutorThreadCount;
117 } else {
118 return executorThreadCount;
119 }
120 }
121
122 @Override
123 public void dispose() {
124 super.dispose();
125 synchronized (scheduledInstanceCache) {
126 for (var instance : scheduledInstanceCache.values()) {
127 instance.shutdown();
128 }
129 scheduledInstanceCache.clear();
130 }
131 }
132
133 private static class Factory implements ThreadFactory {
134 // We have to explicitly store the {@link ThreadGroup} to create a {@link ThreadFactory}.
135 @SuppressWarnings("squid:S3014")
136 private final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
137 private final AtomicInteger threadId = new AtomicInteger(1);
138 private final String namePrefix;
139 private final int priority;
140
141 public Factory(String name, int priority) {
142 namePrefix = name + "-thread-";
143 this.priority = priority;
144 }
145
146 @Override
147 public Thread newThread(@NotNull Runnable runnable) {
148 var thread = new Thread(threadGroup, runnable, namePrefix + threadId.getAndIncrement());
149 if (thread.isDaemon()) {
150 thread.setDaemon(false);
151 }
152 if (thread.getPriority() != priority) {
153 thread.setPriority(priority);
154 }
155 return thread;
156 }
157 }
158}